-E停在预处理,展开宏和头文件;-S停在编译,生成.s汇编文件;-c停在汇编,生成.o目标文件,三者分别对应GCC编译流程的前三个阶段。

编译到哪一步?用 -E、-S、-c 控制流程
GCC 默认一口气走完预处理 → 编译 → 汇编 → 链接,但调试或排查问题时,你往往只想看其中某一步。关键不是“能不能”,而是“该停在哪”。
常见错误现象:fatal error: mymath.h: No such file or directory —— 这其实是预处理阶段就失败了,但你如果直接跑 gcc main.c -o app,报错信息会掩盖真正的问题源头。
-
-E:只做预处理,输出宏展开、#include后的完整代码(默认 stdout,加-o main.i保存)。适合查宏定义冲突、头文件路径是否真被包含进来 -
-S:生成.s汇编文件。注意:它依赖预处理和编译完成,所以如果头文件路径不对,-S一样会失败——别跳过-E直接试-S -
-c:生成.o目标文件,不链接。多个源文件时必须用它分步编译,否则gcc a.c b.c -o app会重复编译、浪费时间
头文件和库怎么找?-I、-L、-l 的顺序和陷阱
项目一复杂,头文件和库必然不在默认路径下。-I、-L、-l 看似简单,但顺序错一点,undefined reference 或 cannot find -lxxx 就跟着来了。
使用场景:你写了 mymath.h 放在 ./include,实现了 libmymath.so 放在 ./lib,主程序要调用它。
-
-I./include必须放在源文件之前,比如gcc -I./include src/main.c -o app;写成gcc src/main.c -I./include也能工作,但不推荐——GCC 对参数顺序虽宽松,但-I和-L紧贴对应文件更安全 -
-L./lib -lmymath中,-L必须在-l之前,且-l写的是库名(去掉lib前缀和.so后缀),不是文件名。写成-l./lib/libmymath.so是错的 - 多个
-L时,GCC 从左到右搜索,遇到第一个匹配的库就停。如果你同时有系统/usr/lib/libm.so和本地./lib/libm.so,把-L./lib放前面才能优先用本地版
动态库必须加 -fPIC,但静态库不一定
写 gcc -shared -o libfoo.so foo.c 报错 relocation R_X86_64_32 against `.rodata' can not be used when making a shared object?八成漏了 -fPIC。
根本原因:动态库加载地址不固定,代码必须位置无关;而静态库 .a 是直接塞进可执行文件的,链接时已确定地址。
- 所有用于生成
.so的目标文件(.o)都必须用-fPIC编译,包括你自己写的和第三方提供的源码。只对最终-shared加-fPIC没用 -
-fPIC和-fpic不通用:-fPIC安全但略慢,适用于所有架构;-fpic在某些平台(如 x86)有 GOT 大小限制,生产环境一律用-fPIC - 静态库
.a如果只被静态链接(gcc main.c libfoo.a -o app),不用-fPIC;但如果这个.a将来会被其他动态库链接(比如你提供 SDK 给别人做.so),就必须用-fPIC编译它的每个.o
警告当错误用?-Wall 和 -Werror 的真实代价
-Werror 看起来很干净,但上线前突然因为一条新警告(比如 GCC 升级后新增的 -Wstringop-overflow)导致构建失败,没人想半夜爬起来改代码。
它不该是默认开关,而是一个阶段性质量门禁。
-
-Wall是底线,必须开。它捕获未使用变量、隐式类型转换、格式化字符串不匹配等高频低级错误 -
-Werror推荐只在 CI 流水线或发布分支启用,开发机上关掉——否则printf("%s", NULL)这种明显 bug 会被拦住,但int x; return x;这类未初始化变量可能逃过-Wall,得靠-Wuninitialized或静态分析工具 - 有些警告没那么可靠:
-Wshadow会抱怨形参名和全局变量同名,但实际运行没问题;-Wfloat-equal警告浮点数相等比较,可有时就是需要这么做。真要用-Werror,得配合-Wno-xxx白名单过滤
动态库的 -fPIC、链接时的 -L/-l 顺序、警告升级的适用阶段——这些不是“知道就行”的知识点,而是每天敲命令时立刻要判断的决策点。参数本身没难度,难的是在具体上下文中选对组合。











