clang -c 一步生成 .o 最常用可靠,内部调用 llc 和 as 但不暴露 .s;llc --filetype=obj 可从 IR 直出 .o,适用于调试或定制流程,需指定 -mtriple。

clang 默认走完整流程(IR → 汇编文本 → 目标文件),但你完全可以跳过 .s 这一层,让 LLVM 后端直接生成二进制目标文件(.o)——关键不是“绕过”,而是控制哪一环负责生成 .o。
用 clang -c 一步生成 .o 最常用也最可靠
这是绝大多数场景的正确做法:clang 作为驱动,内部调用 llc 和 as,但不暴露中间 .s 文件。你只需:
-
clang -c func.c -o func.o:默认行为,直接输出 ELF/COFF.o,不写磁盘汇编文件 - 加
-target可指定架构,比如-target aarch64-linux-gnu,仍不产生.s - 加
-O2或-flto不影响输出形式,优化在 IR 或机器码阶段做,不强制落地为文本汇编
注意:-c 是关键开关;没它,clang 默认尝试链接,不会停在 .o 阶段。
用 llc 直接从 IR 生成 .o,适合调试或定制流程
当你已有 .bc 或 .ll 文件(比如从 clang -emit-llvm -c 得到),想跳过汇编器、由 llc 直出 .o,必须显式指定 --filetype=obj:
llc -mtriple=x86_64-pc-linux-gnu func.bc --filetype=obj -o func.o-
llc不默认生成.o,不加--filetype=obj它会输出.s(即使输入是.bc) -
-mtriple必须匹配目标平台,否则llc可能报错或生成无效.o,例如llc func.bc -o func.o会失败
这种路径常见于 Pass 开发后验证:改完 IR → opt 保存 → llc --filetype=obj 看是否还能正常生成目标码。
为什么有时还是看到 .s 被生成?检查这几个点
你以为跳过了汇编文本,结果发现磁盘上多出 .s 文件,大概率是下面某个操作触发了:
- 用了
-S:这个选项明确要求输出汇编文本,和-c互斥,clang -S -c foo.c会忽略-c - 用了
clang -emit-llvm -S:这生成的是.ll(IR 文本),不是机器汇编,但名字容易混淆 - 构建系统硬编码调用了
as:比如 Makefile 里写死$(CC) -S; $(AS) -o $@ $<,那.s就绕不开 -
llc版本太老:旧版llc对--filetype=obj支持不稳定,建议用 LLVM 14+
llc --filetype=obj 生成的 .o 和 clang -c 生成的在内容上等价,但前者不经过 assembler(as),后者内部仍调用 as —— 区别只在工具链分工,不影响最终目标文件可链接性。
真正容易被忽略的是:clang -c 生成的 .o 已包含重定位信息和符号表,可直接喂给 ld.lld;而如果你手动生成 .ll 再用 llc --filetype=obj,务必确认 IR 中没有未定义的外部符号(比如忘了 #include <stdio.h>),否则 llc 会静默生成一个不可链接的 .o,错误要等到 ld.lld 阶段才暴露。

















