<p>-E 选项用于仅执行预处理:展开宏、插入头文件、处理条件编译,输出为 .i(C)或 .ii(C++)文件;必须用 -o 指定输出,不可重定向;查看 ^# 1 行可回溯原始位置。</p>

能分步,而且必须分步——尤其在调试宏展开、头文件冲突、符号未定义时,跳过某一步反而更难定位问题。
怎么用 -E 只做预处理并保存结果
预处理不是“看看而已”,它是真实文本替换过程,#include 展开后可能上万行,#define 替换错一个地方就会导致后续编译失败但报错位置飘忽。直接看 .i 文件最准。
-
g++ -E main.cpp -o main.ii:生成 C++ 预处理文件(注意后缀是.ii,不是.i;gcc对.c用.i,g++对.cpp默认输出.ii) -
gcc -E -I./inc -DDEBUG main.c -o main.i:加-I指定头文件路径,-D命令行定义宏,避免改源码反复重试 - 别用
g++ -E main.cpp > main.i:C++ 预处理输出含空字符或编码异常,重定向可能损坏内容;务必用-o - 打开
main.ii后搜^# 1(行首井号加空格加数字),那是预处理器插入的行号标记,能帮你回溯原始位置
为什么 -S 生成的 .s 文件里看不到你写的函数名
因为编译阶段做了名字修饰(name mangling)——C++ 函数名被编码成带类型信息的长符号,比如 void foo(int) 可能变成 _Z3fooi。这不是 bug,是链接时区分重载函数的必需机制。
-
g++ -S main.cpp -o main.s输出的是 AT&T 语法汇编(Linux 默认),函数入口标号就是修饰后的名字 -
gcc -S main.c -o main.s不修饰,main就是main,所以混用 C/C++ 源码时,链接阶段容易因符号不匹配报undefined reference - 想看未修饰的 C 风格符号?在 C++ 里用
extern "C"包裹声明,或编译时加-fno-rtti -fno-exceptions简化修饰规则(但不推荐用于生产)
-c 生成 .o 后为什么还不能运行
.o 是纯机器码,但没解决外部引用——比如你调了 std::cout,.o 里只记了个未定义符号 _ZSt4cout,没填实际地址。链接器才是负责“填空”的角色。
-
g++ -c main.cpp -o main.o:这步只产出目标文件,不查库、不验符号、不生成入口点 -
nm main.o能看到U _ZSt4cout(U表示 undefined),而nm /usr/lib/x86_64-linux-gnu/libstdc++.a | grep cout才能找到定义 - 多个
.o之间互相调用函数,也得靠链接器解析——所以大型项目改一个.cpp,只-c它,再链接,就能跳过其余文件的重复编译
链接时用 gcc 还是 g++?
用 g++。即使所有源码都是 C 风格,只要用了 C++ 标准库(哪怕只写了 #include <iostream>),gcc 默认不链 libstdc++,必报一堆 undefined reference to `std::...。
-
g++ main.o -o main:自动加-lstdc++和 C++ runtime 启动代码(如__libc_start_main的 C++ 封装) -
gcc main.o -lstdc++:能过,但顺序敏感——-lstdc++必须放main.o之后,否则链接器扫不到对它的引用 - 静态链接加
-static-libstdc++,否则默认动态链接,运行时依赖系统libstdc++.so
真正容易被忽略的点:预处理和编译阶段看似“不生成机器码”,但它们决定符号是否存在、类型是否匹配、模板能否实例化——很多链接错误,根源其实在 .ii 或 .s 阶段就埋下了,只是延迟暴露。


















