4个阶段
C++程序的编译过程是一个多阶段的复杂过程,将人类可读的源代码转换为机器可执行的目标代码。主要分为以下四个阶段:


- 预处理 (Preprocessing)
功能:- 处理所有以#开头的预处理指令
- 展开宏定义
- 包含头文件内容
-
条件编译
主要操作:
- #include - 将指定文件内容插入到该指令位置
- #define - 定义宏
-
#ifdef/#ifndef/#endif - 条件编译
- #pragma - 编译器特定指令
输入文件:
- .cpp, .hpp, .h等源代码文件
输出文件:
- .i (预处理后的文本文件)
-
编译 (Compilation) --- 编译器
功能:
- 将预处理后的代码转换为汇编代码
- 进行语法和语义分析
- 生成中间表示(IR)
- 优化代码
主要操作:
- 词法分析 - 将源代码分解为标记(token)
- 语法分析 - 构建抽象语法树(AST)
- 语义分析 - 检查类型、作用域等
- 代码生成 - 产生目标平台的汇编代码
输入文件:
- .i文件
输出文件:
- .s (汇编代码文件)
-
汇编 (Assembly) --- 汇编器
功能:
- 将汇编代码转换为机器码
- 生成可重定位的目标文件
主要操作:
- 汇编器读取由编译器生成的汇编代码文件
- 汇编器逐行读取汇编代码,并将每条汇编指令转换成对应的机器指令
- 汇编器记录所有定义的符号及其地址,并生成符号表
- 汇编器记录需要在链接时进行重定位的部分,并生成重定位信息
- 汇编器将所有信息打包成一个目标文件,通常是一个二进制文件,如 .o 文件
输入文件:
- .s文件
输出文件:
- .o或.obj (目标文件)
-
链接 (Linking) --- 链接器
功能:
- 将多个目标文件和库文件合并
- 解析符号引用
- 生成最终可执行文件
主要操作:
- 收集目标文件 - 链接器收集所有需要链接的目标文件(.o 文件)和库文件(如 .a 或 .so 文件)。
- 符号解析 - 确保所有符号都有定义。链接器解析各个目标文件中的符号引用,并查找相应的符号定义。
如果一个目标文件中引用了一个符号,链接器会在其他目标文件或库文件中查找该符号的定义。
如果找不到某个符号的定义,链接器会产生一个错误,指出未定义的符号。 - 重定位 - 调整代码和数据地址。链接器根据最终的布局对代码和数据进行重定位。
这涉及到更新目标文件中的重定位信息,确保符号在最终的可执行文件或库文件中具有正确的地址。 - 合并符号表 - 链接器合并所有目标文件中的符号表,生成一个全局的符号表。
确保每个符号在整个程序中唯一,并且可以被正确引用。 - 插入库文件 - 如果程序依赖于外部库,链接器会插入这些库文件中的相关部分。
动态库(如 .so 文件)通常在运行时加载,而静态库(如 .a 文件)则在链接时直接插入到最终的可执行文件中。 - 生成最终文件 - 链接器将所有处理后的代码和数据组合成一个可执行文件或库文件。
可执行文件(如 .exe 文件在 Windows 中,.out 或直接为程序名称在 Linux 中)包含了所有必要的信息,使得程序能够在目标平台上运行。
输入文件:
- 多个.o文件和库文件
输出文件:
- .exe(Windows)或可执行文件(Unix-like)
完整编译流程示例
shell
main.cpp + other.cpp + headers
↓ 预处理
main.i + other.i
↓ 编译
main.s + other.s
↓ 汇编
main.o + other.o
↓ 链接
executable
常用编译器命令示例
GCC/Clang
shell
# 完整编译过程
g++ main.cpp other.cpp -o program
# 分步编译
g++ -E main.cpp -o main.i # 预处理
g++ -S main.i -o main.s # 编译
g++ -c main.s -o main.o # 汇编
g++ main.o other.o -o program # 链接
MSVC (Visual Studio)
shell
cl /EHsc /c main.cpp # 编译
cl /EHsc /c other.cpp # 编译
link main.obj other.obj /OUT:program.exe # 链接
理解C++编译过程对于调试复杂问题、优化程序性能和构建大型项目至关重要
GCC 常用编译选项
| 常用选项 | 描述 |
|---|---|
| -E | 预处理,开发过程中想快速确定某个宏可以使用"-E -dM" |
| -S | 编译 |
| -c | 把预处理、编译、汇编都做了,但是不链接 |
| -o | 指定输出文件 |
| -I | 指定头文件目录 |
| -L | 指定链接时库文件目录 |
| -l | 指定链接哪一个库文件 |
6个阶段
如果按照更细的粒度划分,C++ 编译过程可以分为以下 6个阶段:
-
预处理(Preprocessing)
处理 #include、#define、#ifdef 等宏指令
展开头文件,生成 纯C++代码(.i 文件)
-
词法分析(Lexical Analysis / Tokenization)
将源代码拆解成 Token(标记),如关键字、标识符、运算符等
例如:int a = 10; → int a = 10 ;
-
语法分析(Syntax Analysis / Parsing)
将 Token 组织成 抽象语法树(AST, Abstract Syntax Tree)
检查语法是否正确(如括号匹配、语句结构)
-
语义分析(Semantic Analysis)
检查变量类型、作用域、函数重载等是否符合语义规则
例如:int a = "hello"; 会报错(类型不匹配)
-
代码生成与优化(Code Generation & Optimization)
将 AST 转换为 目标机器码(汇编代码 .s 文件)
进行优化(如常量折叠、死代码删除、循环优化等)
-
汇编与链接(Assembly & Linking)
汇编:将汇编代码(.s)转换为机器码(.o 或 .obj)
链接:合并多个 .o 文件,解析外部符号,生成可执行文件(.exe 或 .out)
为什么会有不同划分方式?
-
编译器实现不同
某些编译器(如 GCC、Clang、MSVC)可能内部阶段划分略有不同。
例如,GCC 的 -fdump-tree-all 可以输出中间表示(IR),能看到更多细节。
-
教材/课程侧重点不同
编译原理课程可能更关注 前端(词法、语法、语义分析),而工程上更关注 预处理、编译、链接。
-
优化阶段可能单独列出
现代编译器(如 LLVM)会在多个阶段进行优化,因此优化可能被视为独立步骤。
总结
| 4阶段(传统) | 6阶段(细化) | 主要任务 |
|---|---|---|
| 预处理 | 预处理 | 宏扩展、头文件包含 |
| 编译 | 词法分析 | 源代码-->Token |
| 语法分析 | Token-->AST | |
| 语义分析 | 类型检查、作用域分析 | |
| 代码生成与优化 | AST-->汇编代码+优化 | |
| 汇编 | 汇编 | 汇编代码-->机器码(.o) |
| 链接 | 链接 | 合并.o,解析符合,生成可执行文件 |
本质上,6阶段划分只是把"编译"阶段拆解得更细,核心流程仍然是:
源代码 → 预处理 → 编译(前端+后端)→ 汇编 → 链接 → 可执行文件
如果你在某个资料里看到6阶段划分,那很可能是为了更详细地解释编译器的内部工作原理。