云计算百科
云计算领域专业知识百科平台

C++ 从预处理到生成可执行文件简单介绍和中间文件查看

在 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)
    未经允许不得转载:网硕互联帮助中心 » C++ 从预处理到生成可执行文件简单介绍和中间文件查看
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!