C/C++ 程序从源码到可执行文件,必须经过 预处理 → 编译 → 汇编 → 链接 四个阶段。下面我用最清晰、最底层、最工程化的方式,把每个阶段做什么、输入输出、核心功能讲透,带示例,让你彻底理解。
一、预处理(Preprocessing)
阶段:源码 → 预处理后的源码(.i 文件)
工具:预处理器(cpp)
核心:文本替换、宏展开、头文件展开、条件编译
预处理做什么?
1. 删除注释
2. 展开 #include 头文件(把 .h 内容直接插入)
3. 展开 #define 宏(文本替换)
4. 处理 #ifdef / #if / #else / #endif 条件编译
5. 添加行号、文件名信息(给编译器报错用)
示例
test.c
c
#include <stdio.h>
#define MAX 100
int main() {
int a = MAX;
// 注释
return 0;
}
预处理后 test.i(简化):
c
// 一大堆 stdio.h 内容被插入
...
int main() {
int a = 100;
return 0;
}
总结一句话
预处理 = 无脑文本替换 + 条件裁剪
不做语法检查,不做类型检查,纯文本处理。
二、编译(Compilation)
阶段:预处理源码 → 汇编代码(.s 文件)
工具:编译器(cc1、g++)
核心:语法分析 → 语义分析 → 生成汇编
编译做什么?
- 词法分析:把代码拆成 token(关键字、标识符、符号)
- 语法分析:检查语法是否正确(生成 AST 抽象语法树)
- 语义分析:类型检查、函数声明检查、作用域检查
- 中间代码生成(IR)
- 优化(常量折叠、死代码删除、循环优化)
- 生成汇编代码(.s)
示例
test.i → 编译 → test.s(汇编)
asm
main:
pushq %rbp
movl $100, -4(%rbp)
movl $0, %eax
popq %rbp
ret
总结一句话
编译 = 把高级语言翻译成汇编语言 + 做大量优化
三、汇编(Assembly)
阶段:汇编代码 → 目标文件(.o / .obj)
工具:汇编器(as)
核心:把汇编指令翻译成机器码(二进制)
汇编做什么?
1. 把汇编指令 → 二进制机器码
2. 生成符号表(函数名、变量名 → 地址占位符)
3. 生成重定位信息(告诉链接器哪些地址需要修正)
4. 不解析外部符号(如 printf)
输出
test.o(二进制文件,不可直接运行)
总结一句话
汇编 = 翻译成人话就是"把文字指令变成 CPU 能懂的 01 串"
四、链接(Linking)
阶段:多个 .o 文件 + 库文件 → 可执行文件(ELF/PE)
工具:链接器(ld)
核心:符号解析 + 重定位 + 合并段
链接做什么?
1. 符号解析
找到所有未定义的符号(如 printf ),去库文件(libc.so、.a)找定义。
2. 重定位
把所有"占位符地址"替换成真实虚拟地址。
3. 合并段
把所有 .o 的 .text / .data / .rodata / .bss 合并成最终可执行文件的段。
4. 处理库
静态链接(.a):把库代码打包进可执行文件
动态链接(.so):只记录依赖,运行时加载
示例
test.o + libc.so → 链接 → a.out(可执行)
总结一句话
链接 = 把所有模块拼起来,把"未知地址"填成真实地址
五、四个阶段完整流程(最清晰总结)
plaintext
test.c
↓ 预处理(#include、宏、条件编译)
test.i
↓ 编译(语法→语义→优化→汇编)
test.s
↓ 汇编(汇编→机器码→.o)
test.o
↓ 链接(符号解析+重定位+合并段)
a.out(可执行)
六、每个阶段的输入输出、工具、核心任务(表格版)
| 阶段 | 输入 | 输出 | 工具 | 核心任务 |
|---|---|---|---|---|
| 预处理 | .c 源文件 |
.i 文件 |
cpp |
头文件展开、宏替换、删注释、条件编译 |
| 编译 | .i 文件 |
.s 文件 |
cc1/g++ |
语法/语义分析、优化、生成汇编 |
| 汇编 | .s 文件 |
.o 文件 |
as |
汇编→机器码、符号表、重定位 |
| 链接 | .o + .a/.so |
可执行文件 | ld |
符号解析、重定位、合并段、处理库 |
七、最关键的底层概念(必须懂)
1. 预处理不检查语法
2. 编译不处理外部函数(如 printf)
3. 汇编只生成机器码,不填真实地址
4. 链接才是真正把所有模块"拼起来"的阶段
5. 可执行文件 = 链接后的最终产物
八、如果你想看真实命令(GCC 演示)
bash
预处理
gcc -E test.c -o test.i
编译(生成汇编)
gcc -S test.i -o test.s
汇编(生成.o)
gcc -c test.s -o test.o
链接
gcc test.o -o a.out