2. 使用Clang编译C程序并查看中间表示

任务目标

编写一个简单的C程序,使用Clang将其编译为LLVM IR(中间表示),观察C源码到IR的映射关系,理解Clang前端如何将高级语言转换为与目标机器无关的中间表示。

环境准备

需要已安装Clang/LLVM工具链。验证安装:

clang --version

实现步骤

创建C源文件 add.c,包含一个简单的加法函数和主函数:

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

int main() {
    int x = 3;
    int y = 4;
    return add(x, y);
}

执行以下命令生成LLVM IR(文本形式):

clang -S -emit-llvm add.c -o add.ll

查看生成的 add.ll 文件内容:

cat add.ll

预期输出与解读

生成的 add.ll 应包含类似以下内容(不同Clang版本可能有细微差异):

; ModuleID = 'add.c'
source_filename = "add.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"

; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @add(i32 noundef %0, i32 noundef %1) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  store i32 %0, i32* %3, align 4
  store i32 %1, i32* %4, align 4
  %5 = load i32, i32* %3, align 4
  %6 = load i32, i32* %4, align 4
  %7 = add nsw i32 %5, %6
  ret i32 %7
}

; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main() #0 {
  %1 = alloca i32, align 4
  %2 = alloca i32, align 4
  %3 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  store i32 3, i32* %2, align 4
  store i32 4, i32* %3, align 4
  %4 = load i32, i32* %2, align 4
  %5 = load i32, i32* %3, align 4
  %6 = call i32 @add(i32 noundef %4, i32 noundef %5)
  ret i32 %6
}

关键观察点:

  • @add 和 @main 对应C中的两个函数,i32 表示32位整数类型
  • alloca 在栈上分配局部变量空间,store/load 完成内存读写
  • add nsw 中的 nsw 表示“无符号回绕”(no signed wrap)标志
  • call 指令对应C中的函数调用
  • IR采用静态单赋值(SSA)形式,每个变量只赋值一次

验证方法

确认以下条件即表示成功:

  1. clang -S -emit-llvm 命令无报错退出,生成 add.ll 文件
  2. 文件中包含 @add 和 @main 两个函数定义
  3. IR中的运算逻辑与C源码一致:add 函数执行两个参数相加,main 调用 add(3, 4) 并返回结果

如需进一步验证IR可执行,可将其编译为可执行文件并运行:

llc add.ll -o add.s
gcc add.s -o add
./add
echo $?   # 应输出 7

最后一条命令输出 7 说明IR生成正确且可被后续阶段正常处理。

常见问题

  • 找不到clang命令:确认LLVM已安装并加入PATH,或使用完整路径如 /usr/local/opt/llvm/bin/clang
  • 生成的是汇编而非IR:检查是否遗漏 -emit-llvm 参数,该参数指示输出LLVM IR而非目标汇编
  • IR中函数名带后缀:使用 -S -emit-llvm 时若未加 -O0,优化器可能修改IR结构;本案例未指定优化级别,默认 -O0 保持IR与源码结构对应