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)形式,每个变量只赋值一次
验证方法
确认以下条件即表示成功:
clang -S -emit-llvm命令无报错退出,生成add.ll文件- 文件中包含
@add和@main两个函数定义 - 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与源码结构对应