1. GCC编译流程概览与中间表示初探

任务目标

编写一个简单的C程序,通过GCC的-v选项观察编译驱动实际调用的子程序,并分别生成预处理文件(.i)、汇编文件(.s)、目标文件(.o)以及GIMPLE和RTL中间表示文件,从而建立对GCC四阶段架构与中间表示的直观认识。

环境准备

需要Linux环境并已安装GCC(含gcc、cc1)。验证安装:

gcc --version
which cc1    # 通常位于 /usr/lib/gcc/x86_64-linux-gnu/<版本>/cc1

示例程序

创建文件 hello.c:

#include <stdio.h>

#define GREETING "Hello, GCC\\n"

int add(int a, int b) {
    return a + b;
}

int main(void) {
    printf(GREETING);
    return add(1, 2);
}

完整实现与运行

以下命令展示了从源码到可执行文件的完整编译流程,并生成各阶段中间产物。在终端中依次执行:

# 1. 观察完整编译流程(预处理→编译→汇编→链接)
gcc -v hello.c -o hello 2>&1 | tee build_log.txt

# 2. 仅预处理(展开头文件与宏),生成 hello.i
gcc -E hello.c -o hello.i

# 3. 生成GIMPLE中间表示(编译器前端与中端输出)
gcc -fdump-tree-gimple hello.c -o /dev/null
# 生成文件:hello.c.005t.gimple

# 4. 生成RTL中间表示(中端与后端接口)
gcc -fdump-rtl-all hello.c -o /dev/null
# 生成文件:hello.c.<编号>r.expand(含多个RTL转储)

# 5. 仅编译(生成汇编),生成 hello.s
gcc -S hello.i -o hello.s

# 6. 仅汇编(生成目标文件),生成 hello.o
gcc -c hello.s -o hello.o

# 7. 仅链接(生成可执行文件)
gcc hello.o -o hello

# 8. 运行验证
./hello
echo "退出码: $?"

关键观察点与验证方法

打开 build_log.txt,应能看到类似以下顺序的驱动调用(实际路径因系统而异):

gcc -v hello.c -o hello
...
 /usr/lib/gcc/.../cc1 -E -quiet hello.c -o hello.i   # 预处理
 /usr/lib/gcc/.../cc1 -fpreprocessed hello.i ...     # 编译(生成汇编)
 as -o hello.o hello.s                                # 汇编
 /usr/lib/gcc/.../collect2 ...                        # 链接

验证通过条件:

  • build_log.txt 中依次出现 cc1(预处理+编译)、as(汇编)、collect2 或 ld(链接)的调用行,且顺序正确。
  • hello.i 中 #include <stdio.h> 已被展开,GREETING 宏被替换为字符串字面量。
  • hello.c.005t.gimple 中包含函数 add 和 main 的GIMPLE表示,形如:
;; Function add (add, funcdef_no=0)
add (int a, int b)
{
  int D.xxxx;
  D.xxxx = a + b;
  return D.xxxx;
}
  • hello.c.*r.expand 文件中包含RTL指令序列,每行以 (insn ...) 或 (note ...) 开头,寄存器以 rxx 或 %eax 等形式出现。
  • 运行 ./hello 输出 Hello, GCC,退出码为 3(即 add(1,2) 的返回值)。

中间表示文件说明

文件后缀阶段内容特征
.i预处理后头文件已展开、宏已替换,仍是C源码
.gimpleGIMPLE三地址码形式,控制流显式化,表达式被拆分为简单语句
.expand(RTL)RTL与机器描述相关的低级中间表示,含伪寄存器与指令模式
.s汇编目标架构的汇编代码
.o目标文件机器码+符号表,尚未链接

常见问题

  • 找不到 cc1 路径:在 build_log.txt 中搜索 cc1 即可获得完整路径,该路径由GCC内部驱动管理,无需手动调用。
  • RTL转储文件过多:-fdump-rtl-all 会生成多个编号文件(如 .r.expand、.r.vartrack 等)。本案例只需关注 expand 阶段,即RTL生成的最初形式。
  • GIMPLE转储编号可能不同:005t 中的数字可能因GCC版本略有差异,使用通配符查看即可:ls hello.c.*.gimple。
预处理 cc1 -E 编译 cc1 → GIMPLE/RTL 汇编 as 链接 collect2/ld hello.c hello.i hello.s hello.o GCC中间表示(IR) • GIMPLE:三地址码,与语言/机器无关 • RTL:与机器描述相关,含伪寄存器 • 转储命令:-fdump-tree-gimple / -fdump-rtl-all hello(可执行) 运行 → Hello, GCC 观察命令: gcc -v hello.c -o hello # 查看完整驱动流程 gcc -fdump-tree-gimple -fdump-rtl-all hello.c # 生成IR转储