C++程序从.cpp到可执行文件需经历预处理、编译、汇编、链接四阶段:预处理展开宏与头文件生成.i文件;编译生成汇编.s文件;汇编生成目标.o文件;链接合并符号并重定位生成可执行文件。
一个C++程序从 .cpp 文件变成可直接运行的 ./a.out,要经历四个关键阶段:预处理 → 编译 → 汇编 → 链接。这不是简单的“一键构建”,而是分步转化、层层抽象的过程,每一步都解决不同层次的问题。
编译器调用预处理器(如 cpp),对源文件做纯文本替换和展开:
#include 或 #include "xxx.h" 替换成对应头文件的实际内容(递归展开)#define 宏定义(包括函数式宏),删除注释,处理 #ifdef/#ifndef 等条件编译指令main.i),里面全是展开后的 C++ 代码,没有宏、没有头文件名、也没有注释你可以用 g++ -E main.cpp -o main.i 手动触发这一步,打开 main.i 就能看到上千行“膨胀”后的代码——这就是后续所有工作的真正输入。
编译器(如 g++ 的前端)分析 .i 文件的语法、语义,做类型检查、模板实例化、内联展开、优化(如 -O2),最后生|成人|类可读的汇编代码(.s 文件):
_Z3addii 是 int add(int, int) 的 mangled 名)printf)保留为符号引用,不关心具体地址命令示例:g++ -S main.i -o main.s。此时还不能执行,因为汇编指令里写的都是符号名,不是内存地址。
汇编器(如 as)把 .s 文件逐行翻译成二进制机器指令,打包成可重定位的目标文件(.o 文件):
.o 文件包含:机器码段(.text)、已初始化数据(.data)、未初始化数据(.bss)、符号表(含全局函数/变量名及其大小、是否已定义)、重定位表(记录哪些地址需后期修正)std::cout、malloc)仍保持“占位”,地址留空.o 文件还不是完整程序,不能直接运行(缺少入口、无动态链接信息、符号未解析)命令示例:g++ -c main.s -o main.o 或直接 g++ -c main.cpp 跳过前两步。
链接器(如 ld)是“拼图大师”,它把多个 .o 文件 + 静态库(.a)+ 动态库(.so/.dll)组合起来:
call _Z3addii 找到对应 .text 段里的实际地址;把 extern int x 绑定到某个 .o 中定义的 x
mov rax, QWORD PTR x[rip] 中的偏移算准)命令示例:g++ main.o utils.o -o program。若用到标准库,链接器会自动拉入 libc.a 或插入动态链接信息。
基本上就这些。预处理管文本、编译管逻辑、汇编管指令、链接管整合——四步环环相扣,缺一不可。理解它们,才能看懂 “undefined reference”、”multiple definition”、”segmentation fault at startup” 这些底层报错到底发生在哪一层。