在 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++ 编译过程中中间文件的内容查看

主要的内容如下:

  1. .i 文件(预处理后)
  2. .s 文件(汇编代码)
  3. .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.igrep 过滤查看关键代码。


第二步:查看 .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 节区

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐