C++编译核心概念

43 阅读3分钟

C++ 的“编译”不是一步完成,而是一条四阶段流水线 + 两种产物(静态/动态库) + 一个最终可执行文件
把整条线串起来,就能一眼看出“入口文件”“子模块”“静态库/动态库”各自在哪一环出现、起什么作用。


一、四阶段流水线(核心概念)

  1. 预处理(Preprocessing)
    只做文本替换:把 #include 的头文件内容整块拷贝进来,展开宏、去掉注释,生成 .ii 中间文件。
    关键概念:翻译单元(translation unit)——一个 .cpp 经过预处理后的“完整源码”。

  2. 编译(Compilation)
    对每个翻译单元做词法→语法→语义分析→优化,生成汇编文件 .s
    此时才开始“懂 C++”:类、模板实例化、函数重载决议、内联、 constexpr 求值等。

  3. 汇编(Assembly)
    把汇编文本转成目标文件 .o / .obj:里面是机器码 + 未决议符号表(还缺别人实现)。

  4. 链接(Linking)
    把所有 .o(静态 .a/.lib 或动态 .so/.dll)拼在一起,填上地址,生成

    • 最终可执行文件(ELF/PE/Mach-O),或
    • 新的静态库(只是 .o 的归档),或
    • 新的动态库(带导出符号表,供后续动态链接)。

二、产物视角:静态库 vs 动态库 vs 可执行文件

产物生成命令(GCC/Clang)内容使用场景
静态库 libfoo.aar rcs libfoo.a a.o b.o一堆 .o 的压缩归档链接阶段被完整拷贝进最终可执行文件;部署时不需要再带库文件。
动态库 libfoo.so / .dllg++ -shared -fPIC a.o b.o -o libfoo.so机器码 + 导出符号表 + 重定位信息可执行文件里只留“ stubs”;运行时才由操作系统加载器映射到进程地址空间;部署时必须一起发布
可执行文件 appg++ main.o -lfoo -o app入口符号 main + 已决议地址的机器码双击/./app 直接运行。

三、入口到底在哪

  1. 可执行文件
    链接器找的符号是 int main(int argc, char* argv[])(或 main() / WinMain)。
    谁提供这个符号,谁就是“入口源文件”——通常叫 main.cpp / entry.cpp

  2. 静态/动态库
    没有 main!链接器不会把库当成“起点”。库只暴露一组 API 符号供别人调用。
    因此库的“入口”概念是:头文件foo.h)——告诉使用者有哪些类/函数可用。


四、子模块(组件)如何被“包含”

  • 源码层面:用 #include "sub/foo.h" 引入接口;实现文件 sub/foo.cpp 单独编译成 sub/foo.o
  • 构建层面:
    – 手写 Makefile / CMake / GN:把 sub/foo.o 列表一起送进链接器。
    – 或者先把 sub 做成 libsub.a / libsub.so,主程序链接时 -lsub 即可。
  • 头文件只负责编译期检查;库文件负责链接期填符号;运行时动态库还要再加载一次

五、一张图记住全流程

sub/foo.h  ─┐
sub/foo.cpp ├→ 预处理 → 编译 → sub/foo.o ─┐
main.cpp   ─┘                             ├→ 链接 → app(可执行)
                                          │
                                  可选 ar → libsub.a
                                  可选 g++ -shared → libsub.so

一句话总结
C++ 编译的核心是“翻译单元 → 目标文件 → 链接成库或可执行文件”;
只有可执行文件才找 main 当入口,库(静态/动态)只是符号仓库
子模块通过“头文件 + 实现文件 → 目标文件 → 被链接”三步进入最终产物。