交叉文件 #if 和 #endif - 它应该合法吗?

Cross-file #if and #endif - should it be legal?

根据C11标准,

A preprocessing directive of the form

# include "q-char-sequence" new-line

causes the replacement of that directive by the entire contents of the source file identified by the specified sequence between the " delimiters.

所以如果我有一个头文件 test.h 包含:

#endif

和一个源文件 test.c 包含:

#if 1
#include "test.h"

原地替换test.h的内容,不应该按照标准通过预处理阶段吗?

但是我不能用 clang 做到这一点,它说:

In file included from test.c:2:
./test.h:1:2: error: #endif without #if
#endif
 ^
test.c:1:2: error: unterminated conditional directive
#if 1
 ^
2 errors generated.

那么标准规定的行为是什么?

如果您阅读例如this C++ #include reference the included file is first run through translation phases one to four, and phase four 是 运行 预处理器(递归)。

这意味着您包含的文件在 #if#endif 方面必须是完整的。

这在 C 中也会发生。


阅读 C11 规范(ISO/IEC 9899:2011 [2012])后,我认为 是这样的:

编译器处于预处理器阶段 (phase 4) 并评估 #if 1 预处理器指令。条件评估为真,因此它进入条件内的块。看到 #include "test.h" 指令。

当编译器处理包含指令时,它会暂时停止处理当前文件,以处理包含的文件。在继续处理当前源文件之前,对包含文件的此处理会经过编译阶段 1 到 4(含)。

当包含的头文件本身的处理进入阶段4并开始处理#endif指令时,它不会在递归中up inclusion stack to find the matching #if,预处理器只查看当前堆栈帧(当前文件)。因此你得到第一个关于 no #if 的错误。该标准实际上对此没有任何说明。它基本上说的是 #if 必须有一个匹配的 #endif。编译器不会在包含堆栈上查找匹配项 #if 似乎更多是一个实现细节。

无论如何,预处理器在第 4 阶段的第 3 步结束其对头文件的处理,即

At the end of this phase, all preprocessor directives are removed from the source.

所以当控件返回到源文件的预处理时,它实际包含的文件不包含任何预处理指令。基本上,所有包含的都是一个空文件。这导致了第二个错误,即 #if 没有 #endif,因为确实没有。

clang 是正确的:#if/#endif 在不同文件中的这种拆分是不符合要求的。这是因为预处理器首先扫描文件的其余部分以找到相应的 #endif,然后才解析保留的条件部分中存在的其他预处理器指令。

这是 C 标准的第 6.10.1 节 p6。