如何识别c++ -E预处理输出中的各编译单元及相关问题
问题1:合并输出是否为预处理器的简化呈现形式?
猜想完全成立。GCC、Clang 类编译器的-E参数作用是仅执行预处理流程、输出预处理结果,当命令行传入多个.cpp源文件时,预处理器仍然会为每个源文件生成完全独立的编译单元预处理结果,不会做跨单元的内容共享或合并处理,只是最终会按源文件在命令行中出现的顺序,把所有单元的结果依次拼接后输出到标准输出,才会呈现为单份内容。
问题2:如何区分不同编译单元的边界?
你提到的# 数字 "文件名"格式的行是预处理器生成的行标记(linemarker),确实承担了边界标识作用。
这类标记的通用格式为# 行号 "文件名" [状态标记],当出现行号为1、文件名为传入的.cpp源文件、无特殊状态标记的行时,就是一个全新独立编译单元的起始位置。你给出的样例中# 1 "Bar.cpp"、# 1 "Foo.cpp"两行,就是两个编译单元的明确分界点。
除了编译单元边界,这类标记也会标识头文件插入、内置内容加载的边界。
问题3:为什么观察到Foo.cpp似乎没有复制Foo.hpp的内容?
这是观察疏漏导致的误判。你提供的样例中,Foo.cpp对应的编译单元区段内明确包含了Foo.hpp的完整复制内容:在# 1 "Foo.cpp" 2行之后,紧接着就是# 1 "./Foo.hpp" 1标记,后面跟着和Bar.cpp区段完全一致的Foo类定义,之后才通过# 2 "Foo.cpp" 2标记切回Foo.cpp本身的代码,继续输出Foo类的成员函数实现。
这部分重复内容是预处理器处理#include指令的标准行为:无论哪个源文件引入头文件,预处理器都会把头文件的完整文本,直接插入到源文件中#include指令所在的位置。
问题4:<built-in>、<command line>标记的含义
这两个是预处理器定义的虚拟文件标记,不对应磁盘上存储的真实文件:
<built-in>:标识预处理器内置内容的位置,包括编译器预定义的宏、内置类型声明、平台相关的基础定义等内容,标记后附带的数字是状态位,分别代表进入内置上下文、加载系统级内置定义、退出内置上下文切回用户代码的不同阶段。<command line>:标识通过编译命令行参数传入的内容位置,最常见的就是-D参数定义的宏,这部分内容会在加载用户源文件之前被预处理器优先处理。
实用提示:如果需要获取每个源文件对应的独立预处理结果,不要在同一条
-E命令中传入多个源文件,对每个.cpp文件单独执行预处理命令即可,例如分别执行c++ -Wall -Wextra -Werror -E Bar.cpp -o Bar.i、c++ -Wall -Wextra -Werror -E Foo.cpp -o Foo.i,就会生成两个互不干扰的独立预处理输出文件。
本次实验的预处理输出样例
# 1 "Bar.cpp" # 1 "<built-in>" 1 # 1 "<built-in>" 3 # 414 "<built-in>" 3 # 1 "<command line>" 1 # 1 "<built-in>" 2 # 1 "Bar.cpp" 2 # 1 "./Foo.hpp" 1 class Foo { private: int _value; public: Foo( int value); int getValue( void ) const; }; # 4 "Bar.cpp" 2 int main( void ) { Foo foo(2); foo.getValue(); } # 1 "Foo.cpp" # 1 "<built-in>" 1 # 1 "<built-in>" 3 # 414 "<built-in>" 3 # 1 "<command line>" 1 # 1 "<built-in>" 2 # 1 "Foo.cpp" 2 # 1 "./Foo.hpp" 1 class Foo { private: int _value; public: Foo( int value); int getValue( void ) const; }; # 2 "Foo.cpp" 2 Foo::Foo( int value) : _value(value) {}; int Foo::getValue( void ) const { return this->_value ; };
内容的提问来源于stack exchange,提问作者interesting

