
这是系列第五篇。前四篇讲了语法、概念、Bug 和代码模式,都是"怎么读代码"。这一篇讲工程层面——C++ 代码是怎么变成可运行产物的,以及为什么很多跨平台底层选择用 C++。
配套了一个可编译运行的示例工程,建议边看边跑:cpp-build-demo
前端:源码 → 打包器(webpack/vite) → bundle.js → 浏览器解释执行
C++: 源码 → 预处理 → 编译 → 链接 → 机器码产物(直接运行,无需解释器)
阶段 | 做了什么 | 前端类比 |
|---|---|---|
预处理 | 展开 | Babel 转译 JSX → 纯 JS |
编译 | 把 C++ 源码翻译成机器码(与 CPU 架构相关) | TypeScript → JavaScript |
链接 | 把多个 .o 文件和库文件合并成最终产物 | webpack 把各 chunk 合并为 bundle |
cd docs/articles/cpp-build-demo
make step1 # 预处理:看 #include 被展开成什么
make step2 # 编译:生成 .o 目标文件
make step3a # 打包静态库 .a
make step3b # 打包动态库 .so
make step4a # 链接(静态)→ 可执行文件
make step4b # 链接(动态)→ 可执行文件产物 | 扩展名 | 前端类比 | 说明 |
|---|---|---|---|
目标文件 |
| 无 | 单个源文件的编译结果,不能独立运行 |
静态库 |
| 打进 bundle 的依赖 | 多个 .o 打包,链接时复制进最终产物 |
动态库 |
| CDN 外链的 JS | 运行时加载,多个程序共用 |
可执行文件 | 无 / |
| 直接运行的程序 |
你日常使用的很多工具底层都是 C++ 动态库:
开源项目 | 产物 | 你间接用到它的场景 |
|---|---|---|
SQLite |
| 移动端数据库、浏览器 IndexedDB 底层 |
OpenSSL |
| HTTPS 通信、Node.js crypto 模块 |
FFmpeg |
| 音视频处理、各种播放器 |
V8 |
| Node.js、Chrome 的 JS 引擎 |
libuv |
| Node.js 的事件循环和异步 I/O |
skia |
| Chrome、Flutter 的 2D 渲染引擎 |
curl |
| 各种 HTTP 客户端底层 |
当你 npm install sqlite3 时,实际是下载了预编译好的 .node 文件(本质就是一个 .so/.dylib),Node.js 在运行时 dlopen 加载它。

维度 | 静态库 (.a) | 动态库 (.so) |
|---|---|---|
链接时机 | 编译时复制进产物 | 运行时加载 |
部署 | 只需一个文件 | 必须带上 .so 文件 |
体积 | 大(库代码被复制) | 小(库代码共享) |
更新库 | 必须重新编译程序 | 替换 .so 即可 |
前端类比 |
|
|
动态库本身也可能依赖其他动态库,形成链式依赖:
你的程序
└─ 依赖 libcurl.so(HTTP 库)
├─ 依赖 libssl.so(加密)
├─ 依赖 libz.so(压缩)
└─ 依赖 libpthread.so(多线程)可以用 ldd 命令查看一个动态库或可执行文件的所有依赖:
$ ldd ./build/app_dynamic
libmath.so => ./build/libmath.so
libstdc++.so.6 => /usr/lib/libstdc++.so.6
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6
$ ldd /usr/bin/curl
libcurl.so.4 => /usr/lib/libcurl.so.4
libssl.so.3 => /usr/lib/libssl.so.3
libz.so.1 => /usr/lib/libz.so.1
...当你遇到这种运行时错误:
error while loading shared libraries: libxxx.so: cannot open shared object file说明依赖链上某个 .so 找不到。解决方式:
apt install libxxx-dev)/usr/lib 或 LD_LIBRARY_PATH 指定的目录)前端类比: 就像你的 JS 代码 import A from 'a',但 a 这个包内部又 import B from 'b',如果 b 没装,运行时就报 Cannot find module 'b'。
这是前端开发者完全陌生的领域——JS 运行在虚拟机上不关心 CPU,但 C++ 编译出的是特定 CPU 的机器码。
架构 | 用在哪里 | 说明 |
|---|---|---|
| PC、Mac(Intel)、服务器 | 桌面主流 |
| 手机、Mac(M 系列)、新服务器 | 移动端主流 |
| 老手机、IoT 设备 | 32 位 ARM |
| 浏览器 | WebAssembly,C++ 也能编译到这个目标 |
在你的 x86_64 电脑上编译出 arm64 手机能运行的程序,就叫交叉编译:

# 本地编译(产物只能在当前机器跑)
g++ main.cpp -o app
# 交叉编译(在 x86 电脑上编译给 ARM 手机用的产物)
aarch64-linux-gnu-g++ main.cpp -o app_arm64前端类比: 你在 Mac 上 npm run build 出的 JS 哪里都能跑,因为有 JS 引擎兜底。但 C++ 编译出的机器码只能在目标 CPU 上跑——在 Mac 上编译给手机用,就得用交叉编译器。
以 SQLite 为例:
sqlite-android-arm64.so ← 安卓手机用
sqlite-android-x86_64.so ← 安卓模拟器用
sqlite-ios-arm64.a ← iPhone 用
sqlite-macos-arm64.dylib ← Mac 用
sqlite-linux-x86_64.so ← Linux 服务器用同一份 C 源码,交叉编译了 5 次,产出 5 种产物。

核心区别: JS 依赖各平台的 JS 引擎来解释执行。C++ 直接编译为各平台的机器码,不依赖任何 runtime。
原因 | 说明 |
|---|---|
性能 | 编译为机器码直接执行,无解释器开销 |
无 runtime 依赖 | 不需要 JVM / JS 引擎,产物自身就是完整代码 |
全平台编译器支持 | Android NDK / iOS Clang / Linux GCC 等都支持 C++ |
与系统层零开销交互 | 操作系统底层就是 C/C++,无需桥接 |
典型案例:
C++ 没有统一的构建系统和包管理器(不像前端有 npm + webpack 的事实标准)。
工具 | 角色 | 前端类比 |
|---|---|---|
CMake | 项目配置(生成构建文件) |
|
Makefile | 构建执行(调用编译器) |
|
Ninja | 快速构建执行器 |
|
Conan / vcpkg | 包管理 |
|
GN | Google 的构建配置 | Chromium / V8 在用 |
大型项目一般用 CMake,小项目直接用 Makefile。示例工程用的是 Makefile。
前端开发者遇到 C++ 编译报错时,先判断属于哪个阶段:
阶段 | 典型错误 | 前端类比 |
|---|---|---|
预处理 |
|
|
编译 |
| TS 的 |
编译 |
| TS 的 |
链接 |
| 运行时 |
链接错误是前端开发者最不熟悉的——每个 .cpp 单独编译没问题,但合并时发现某个函数"只有声明没有实现"。类比:你在 .d.ts 里声明了类型,但忘了写 .ts 实现。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。