
一个C++程序从写好源代码到变成能双击运行的可执行文件,中间要经过编译(Compilation)和链接(Linking)两个核心阶段。它们不是一步完成的,而是分工明确、层层递进的过程。
预处理:先“展开”所有宏和头文件
编译器第一步不是直接翻译C++语法,而是调用预处理器(如cpp)。它负责处理以#开头的指令:
- #include iostream> → 把iostream头文件的全部内容原样插入到当前源文件中
- #define MAX 100 → 把后续所有出现的MAX替换成100
- #ifdef DEBUG → 根据是否定义了DEBUG,决定保留或删掉某段代码
这一步不检查语法,只做纯文本替换,输出的是一个“.ii”后缀的“已展开”源文件(比如main.ii),为真正编译做好准备。
编译:把C++代码翻译成汇编,再转成机器码(目标文件)
预处理后的文件交给编译器(如g++的前端),完成三件事:
立即学习“C++免费学习笔记(深入)”;
- 词法分析和语法分析:确认代码符合C++语法规则,构建抽象语法树(AST)
- 语义分析:检查类型是否匹配、函数是否声明、变量是否定义等逻辑问题(比如int x = “hello”;会在这里报错)
- 代码生成:把AST翻译成对应平台的汇编代码(如x86-64),再由汇编器转成二进制的目标文件(.o 或 .obj)
注意:每个.cpp文件独立编译,生成一个对应的目标文件。此时函数调用(比如std::cout )还只是“占个位置”,因为实际实现不在本文件里——它被标记为“未定义符号”,留待链接阶段解决。
链接:把多个目标文件和库“拼起来”,填上所有地址
链接器(如ld或gold)把所有.o文件、系统库(如libstdc++.a)、动态库(如libc.so)整合成一个完整可执行文件。它主要做三件事:
- 符号解析:找到每个“未定义符号”的定义位置(比如std::cout在libstdc++.a里,main在main.o里)
- 重定位:各目标文件的代码和数据原本假设从地址0开始,链接器给它们分配真实内存地址,并修正所有跳转、调用、变量访问的地址(比如把call printf@PLT指向正确的PLT表入口)
- 合并段:把所有.text(代码)段合在一起,所有.data(已初始化全局变量)段合在一起,形成最终文件结构
静态链接时,库代码直接复制进可执行文件;动态链接时,只记录依赖关系,运行时由操作系统加载共享库。
小结:整个流程是流水线式的,每步输出是下一步输入
源文件(.cpp)→ 预处理 → .ii → 编译+汇编 → .o → 链接 → 可执行文件(如a.out或.exe)
其中任何一个环节出错(语法错、找不到函数定义、重复定义、库缺失),都会中断流程,不会生成最终结果。
理解这个过程,对排查编译错误、控制构建行为(比如用-c只编译不链接)、优化链接速度(如使用lto)都很有帮助。
基本上就这些。