Clang编译C文件默认执行预处理→编译成IR→后端生成汇编→汇编成目标文件→链接成可执行文件共5步,对应input、preprocessor、compiler、backend、assembler、linker六个阶段(含输入),中间产物为test.i、test.bc/test.ll、test.s、test.o和最终可执行文件。

clang 编译 C 文件时默认走哪几步?
Clang 不是一步到位生成可执行文件,而是按阶段推进:预处理 → 编译成 IR → 后端生成汇编 → 汇编成目标文件 → 链接成可执行文件。你用 clang test.c -o test 看似简单,背后其实隐式完成了全部五步。
验证方式:运行 clang -ccc-print-phases test.c,你会看到类似这样的输出:
+-- 0: input, "test.c", c
+-- 1: preprocessor, {0}, cpp-output
+-- 2: compiler, {1}, ir
+-- 3: backend, {2}, assembler
+-- 4: assembler, {3}, object
+-- 5: linker, {4}, image这五步对应五个中间产物(test.i、test.bc、test.s、test.o、test),但默认不保留——除非你显式加参数。
如何分步生成并检查每个中间文件?
分步控制的关键是参数组合,不是靠删掉某个步骤,而是用开关“停在某步”并输出对应格式:
-
clang -E test.c -o test.i:只做预处理,输出文本形式的test.i(宏展开、头文件插入后的内容) -
clang -S test.c -o test.s:停在汇编阶段,生成人类可读的test.s(x86-64 或 ARM64 汇编,取决于当前平台) -
clang -c test.c -o test.o:停在目标文件阶段,生成二进制test.o(ELF 格式,不可直接执行) -
clang -emit-llvm -S test.c -o test.ll:生成可读的 LLVM IR 文本test.ll,适合看优化前后的中间表示 -
clang -emit-llvm -c test.c -o test.bc:生成二进制 bitcodetest.bc,可用于 LTO(Link-Time Optimization)或跨平台重编译
注意:-S 和 -c 互斥;-E 不能和 -c 同时用;-emit-llvm 必须配合 -S 或 -c 才生效。
为什么 clang test.c 能直接运行,而 clang -c test.c 却不行?
clang test.c -o a.out 默认启用链接器(linker),把 test.o 和标准库(如 libc、crt0.o)一起打包成完整可执行文件;而 clang -c test.c 显式禁用链接,只产出目标文件 test.o,它缺少入口符号(_start)、没有动态链接信息、也没初始化代码段。
Clang 22.1.3 Windows 64 位历史版本安装包,适合旧项目兼容、LLVM/Clang 工具链回退、编译行为对比、链接问题复现和 C/C++ 构建环境维护。
想手动链接:需要补全启动文件和库路径,例如:
clang -c test.c -o test.o clang test.o -o test # 这行会自动调用 ld 并链接标准库
但如果你用 ld test.o -o test 直接链接,大概率报错:undefined reference to `_start`——因为 ld 不带 crt 启动逻辑,而 clang 在调用 ld 时会自动注入 crt1.o、libc.so 等。
常见错误:clang: error: unable to execute command: Segmentation fault
这类崩溃通常不是代码问题,而是 Clang 自身环境异常,集中在以下几种情况:
- 系统 PATH 中混入了不同版本的
clang和llvm-config(比如 Homebrew 装了一个,Xcode 命令行工具又装了一个),导致插件或 pass 加载错乱 - 用了
-fplugin=xxx但插件 ABI 版本不匹配(比如用 LLVM 17 编译的插件被 LLVM 18 的 clang 加载) - 在 macOS 上未运行
xcode-select --install或xcode-select --switch /Applications/Xcode.app,导致 Clang 找不到 SDK 路径,预处理阶段崩溃 - 磁盘空间不足或 /tmp 权限异常,Clang 在生成临时 bitcode 或调试信息时失败
排查优先级:先运行 clang --version 和 llvm-config --version 确认一致;再试 clang -v test.c 看完整命令行和搜索路径;最后检查 clang -### test.c 输出里是否出现可疑的 -resource-dir 或 --sysroot 路径。
真正容易被忽略的是:Clang 的“一步编译”其实是高度封装的流水线,每步的输入/输出格式、依赖关系、错误传播机制都不同。想稳定复现问题或做定制化构建(比如插桩、静态分析、交叉编译),必须理解各阶段产物的语义边界,而不是只盯着最终的可执行文件。

















