C++ 从预处理到生成可执行文件简单介绍和中间文件查看
main.cpp↓ g++ -Emain.i↓ g++ -Smain.s↓ g++ -cmain.o↓ g++main (可执行文件)
·
在 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++ 编译过程中中间文件的内容查看
主要的内容如下:
.i
文件(预处理后).s
文件(汇编代码).o
文件(目标文件,查看符号表、反汇编)
示例代码 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 节区 |
更多推荐
所有评论(0)