-c生成目标文件(.o),仅执行预处理至代码生成,不链接;-s输出汇编文件(.s),停在汇编前;-e仅预处理,展开宏与头文件并输出c代码流。

-c、-S 和 -E 是 Clang/LLVM 编译器前端(clang)控制编译流程阶段的三个关键标志,它们不参与链接,只决定“停在哪一步”。用错会导致后续构建失败或生成意外产物——比如你想要一个目标文件却误用了 -S,结果得到汇编代码而不是 .o,ld 或 lld 就会报 unknown file type。
什么时候该用 -c:只生成目标文件(.o),跳过链接
这是日常构建中最常被正确使用的选项。它让 Clang 完成预处理、词法/语法分析、语义检查、IR 生成、中端优化、后端代码生成,最终输出机器码级别的目标文件(如 main.o),但**不调用链接器**。
- 典型场景:CMake 默认对每个
.c文件执行clang -c -o main.o main.c,为后续链接做准备 - 必须配
-o指定输出名,否则默认输出到a.out(但它是目标文件,不是可执行文件) - 支持
-O2、-march=...等所有优化和目标相关参数,因为后端仍在工作 - 错误示例:
clang -c main.c会静默生成a.out,但它是ELF object,不是可执行文件——容易误以为编译完成了
什么时候该用 -S:停在汇编阶段,输出 .s 文件
它比 -c 多停一步:完成 IR 优化后,后端只生成汇编代码(如 AT&T 或 Intel 语法),不进行汇编(as)和链接。输出是人类可读的文本文件,适合调试代码生成逻辑或检查内联汇编行为。
- 常见用途:确认
__attribute__((noinline))是否生效、看循环是否被向量化、检查volatile访问是否保留 - 默认输出名是
input.s(如main.c→main.s),不会覆盖源码 - 注意:生成的汇编依赖目标 triple 和 ABI,
clang -target aarch64-linux-gnu -S main.c和默认 host 编译出的main.s完全不同 - 陷阱:若忘记删掉旧
.s文件就跑make,有些旧版 Makefile 会误把.s当作源码再传给as,导致重复汇编失败
什么时候该用 -E:只做预处理,展开宏和头文件
它是最轻量的模式,仅运行 C 预处理器(cpp),不做语法检查、不生成 IR、不调用后端。输出是展开后的纯 C 代码流,用于诊断宏定义冲突、头文件包含路径问题或条件编译逻辑。
- 典型问题定位:
#ifdef __x86_64__是否被正确定义?stdint.h最终来自哪里?__attribute__宏是否拼错导致失效? - 输出默认到 stdout,建议重定向:
clang -E main.c > main.i;.i是传统后缀,但内容仍是 C 文本 - 注意:
-I、-D、-U这些预处理相关选项只在此阶段生效;-O、-mcpu等会被忽略(clang 会警告) - 容易被忽略的点:某些嵌入式项目用自定义
clang -target thumbv7m-none-eabi -E查看启动文件宏展开,但若忘了加-isystem指向 CMSIS 头路径,#include "core_cm4.h"就直接报错退出,而非输出部分结果
这三个标志本质是编译流水线的“断点开关”,-E 在最前,-S 居中,-c 最接近完整编译。实际工程中,-c 是构建系统的基石,而 -E 和 -S 是你怀疑“为什么这段代码没按预期生成”时,最先该摸的两个探针——尤其当跨平台或启用新后端(如 RISC-V 或 llvmpipe)时,预处理宏和汇编指令级行为往往比 C 源码更诚实。











