在 C++ 中,从源代码到最终生成可执行文件,会经历多个阶段,主要包括:
总体流程概览
源文件 (.cpp/.h)
↓ [1. 预处理]
预处理后的代码 (.i)
↓ [2. 编译]
汇编代码 (.s)
↓ [3. 汇编]
目标文件 (.o)
↓ [4. 链接]
可执行文件 (a.out 或指定名称)
各阶段详解及示例
下面我们以简单的示例文件 main.cpp 为例:
main.cpp 内容如下:
#include <iostream>
#define PI 3.14
int main() {
std::cout << "Hello, PI = " << PI << std::endl;
return 0;
}
【1】预处理(Preprocessing)
命令:
g++ -E main.cpp -o main.i
功能:
- 展开 #include 文件;
- 替换宏定义(如 PI);
- 处理条件编译;
- 删除注释。
结果文件:
- main.i:纯 C++ 源码,没有任何宏或头文件残留。
【2】编译(Compiling)
命令:
g++ -S main.i -o main.s
功能:
- 将预处理后的代码翻译为汇编代码;
- 进行语法分析、语义检查、生成汇编指令。
结果文件:
- main.s:人类可读的汇编代码。
【3】汇编(Assembling)
命令:
g++ -c main.s -o main.o
功能:
- 将汇编代码转换成机器指令(二进制);
- 生成目标文件(Object File)。
结果文件:
- main.o:二进制格式的目标文件,尚不能独立运行。
【4】链接(Linking)
命令:
g++ main.o -o main
功能:
- 将目标文件与标准库(如 libstdc++)进行链接;
- 解析外部符号(如 std::cout);
- 合并段(.text/.data/.bss);
- 生成最终可执行文件。
结果文件:
- main:可执行二进制文件。
一条命令直接完成全部流程
g++ main.cpp -o main
g++ 会自动按顺序完成:
- 预处理 → 编译 → 汇编 → 链接。
总结:每步输入输出文件关系图
main.cpp
↓ g++ -E
main.i
↓ g++ -S
main.s
↓ g++ -c
main.o
↓ g++
main (可执行文件)
附加说明
预处理 | .i | cpp | 用于宏展开、包含头文件等 |
编译 | .s | cc1plus | C++ 编译器前端,生成汇编 |
汇编 | .o | as | 汇编器,将汇编生成目标代码 |
链接 | 无特定后缀(结果为 .out 或自定义) | ld | 链接器,整合代码与库 |
C++ 编译过程中中间文件的内容查看
主要的内容如下:
示例代码 main.cpp
#include <iostream>
#define PI 3.14
int main() {
std::cout << "PI = " << PI << std::endl;
return 0;
}
第一步:查看 .i 文件(预处理输出)
g++ -E main.cpp -o main.i
查看内容(部分截取):
extern std::ostream cout;
int main() {
std::cout << "PI = " << 3.14 << std::endl;
return 0;
}
分析重点:
- #include <iostream> 被展开成了大量系统头文件;
- 宏 PI 被替换为 3.14;
- 所有注释被移除;
- 条件编译指令已解析完成。
可以通过 less main.i 或 grep 过滤查看关键代码。
第二步:查看 .s 文件(汇编代码)
g++ -S main.cpp -o main.s
查看部分汇编内容(AT&T 语法):
.LC0:
.string "PI = "
.LC1:
.string "\\n"
main:
pushq %rbp
movq %rsp, %rbp
movsd .LC2(%rip), %xmm0
leaq .LC0(%rip), %rdi
call std::operator<<(…)
分析重点:
- .LCx 是常量池字符串;
- 使用 %rip 地址相对寻址;
- xmm0 是浮点寄存器(因为 3.14 是浮点数);
- call 调用 C++ 的重载函数(通过符号表连接);
可以使用 less main.s 或编辑器阅读它,也可以用 objdump 查看汇编后的 .o 文件(下方讲解)。
第三步:查看 .o 文件内容(目标文件)
g++ -c main.cpp -o main.o
1. 查看符号表
nm main.o
示例输出:
0000000000000000 T main
U std::cout
U std::endl(…)
T | 在 .text(代码段)中的全局符号 |
U | 未定义符号(将在链接时解析) |
2. 反汇编 .o 文件
objdump -d main.o
示例输出片段:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: …
这是二进制机器码反汇编出的汇编,方便对比 .s 文件与真实机器码的指令。
3. 查看节区结构(section headers)
readelf -S main.o
输出部分节区说明:
.text | 程序指令(可执行代码) |
.data | 已初始化的数据 |
.bss | 未初始化的数据 |
.rodata | 只读数据(字符串常量等) |
.symtab | 符号表 |
.strtab | 字符串表 |
.rel.text | 用于链接的重定位信息 |
使用建议:
查看预处理代码 | g++ -E main.cpp -o main.i | 纯文本 |
查看汇编代码 | g++ -S main.cpp -o main.s | 纯文本 |
查看符号表 | nm main.o | 查看链接依赖符号 |
反汇编代码 | objdump -d main.o | 二进制→汇编 |
查看段结构 | readelf -S main.o | 查看 ELF 节区 |
评论前必须登录!
注册