我们说我有两个文件kullo
:
a.h
和#if 1
#include "b.h"
:
b.h
gcc和clang的预处理器都拒绝#endif
:
a.h
但是,C标准(N1570 6.10.2.3 )表示:
表单
的预处理指令
$ cpp -ansi -pedantic a.h >/dev/null In file included from a.h:2:0: b.h:1:2: error: #endif without #if #endif ^ a.h:1:0: error: unterminated #if #if 1 ^
导致由
# include "q-char-sequence" new-line
分隔符之间的指定序列标识的源文件的全部内容替换该指令。
似乎允许上面的构造。
gcc和clang在拒绝我的代码方面不合规吗?
答案 0 :(得分:13)
C标准定义了8个翻译阶段。源文件按顺序(或以等效方式)由8个阶段中的每个阶段处理。
N1570第5.1.1.2节中定义的第4阶段是:
执行预处理指令,扩展宏调用, 并执行
_Pragma
一元运算符表达式。如果一个 与通用字符的语法匹配的字符序列 name由标记串联(6.10.3.3)生成,行为是 未定义。#include
预处理指令会导致命名 要从第1阶段到第4阶段处理的标头或源文件, 递归。然后删除所有预处理指令。
这里的相关句子是:
#include
预处理指令会导致命名 要从第1阶段到第4阶段处理的标头或源文件, 递归。
这意味着每个包含的源文件都是自己预处理的。这样就不会在一个文件中包含#if
,在另一个文件中包含相应的#endif
。
(正如评论中提到的“野生大象”,正如rodrigo's answer所述,第6.10节中的语法也表示 if-section ,以{{1开头(或#if
或#ifdef
)行和以#ifndef
行结尾,只能作为预处理文件的一部分出现。)
答案 1 :(得分:8)
我认为编译器是正确的,或者至多标准是不明确的。
诀窍不在于如何实现#include
,而在于完成预处理的顺序。
查看C99标准第6.10节中的语法规则:
preprocessing-file:
group[opt]
group:
group-part
group group-part
group-part:
if-section
control-line
text-line
# non-directive
if-section:
if-group elif-groups[opt] else-group[opt] endif-line
if-group:
# if constant-expression new-line group[opt]
...
control-line:
# include pp-tokens new-line
...
正如您所看到的,#include
内容嵌套在group
内,而group
是#if / #endif
内的内容。
例如,在格式良好的文件中,例如:
#if 1
#include <a.h>
#endif
将解析为#if 1
,加上group
,再加上#endif
。内部group
有一个#include
。
但在你的例子中:
#if 1
#include <a.h>
规则if-section
不适用于此来源,因此甚至不会检查group
作品。
可能你可以争辩说标准是不明确的,因为它没有指定何时发生#include
指令的替换,并且一致的实现可能会改变很多语法规则并替换{{1}失败之前没找到#include
。但是,如果语法的副作用会修改您正在解析的文本,则无法避免这些含糊不清。难道不是很好吗?
答案 2 :(得分:0)
将C预处理器视为一个非常简单的编译器,将文件转换为C预处理器在概念上执行几个阶段。
严格地说,C Standard (ISO/IEC 9899:201x)的§5.1.1.2中提到的与预处理相关的翻译阶段是阶段3和阶段4.阶段3几乎完全对应于词法分析,而阶段4则是关于代码生成。
该图片似乎缺少句法分析(解析)。实际上,C预处理器语法非常简单,真正的预处理器/编译器与词法分析一起执行它。
如果句法分析阶段成功结束 - 即预处理翻译单元中的所有语句根据预处理程序语法是合法的 - 可以进行代码生成并执行所有预处理指令。
执行预处理指令意味着根据文件的语义转换源文件,然后从源文件中删除指令。
每个预处理程序指令的语义在C标准的§6.10.1-6.10.9中指定。
返回您的示例程序,您提供的2个文件,即a.h
和b.h
,在概念上按如下方式处理。
词法分析 - 每个单独的预处理令牌由左侧的“{”和右侧的“}”分隔。
a.h
{#}{if} {1}
{#}{include} {"b.h"}
b.h
{#}{endif}
这个阶段没有错误地执行,其结果,即预处理标记的顺序,被传递到后续阶段:句法分析。
句法分析
下面给出了a.h的暂定推导
preprocessing-file →
group →
group-part →
if-section →
if-group endif-line →
if-group #endif new-line →
…
很明显,a.h的内容不能从预处理语法中导出 - 事实上,终止#endif
已经缺失 - 因此a.h
在语法上不正确。这正是编译器在写入时告诉您的内容
a.h:1:0: error: unterminated #if
b.h
发生类似情况;向后推理,#endif
只能从规则
if-section →
if-group elif-groups[opt] else-group[opt] endif-line
这意味着文件内容应该来自以下3组之一
# if constant-expression new-line group[opt]
# ifdef identifier new-line group[opt]
# ifndef identifier new-line group[opt]
由于情况并非如此,因为b.h
不包含# if/# ifdef/# ifndef
而只包含单#endif
行,b.h
的内容在语法上并不正确编译器以这种方式告诉你
In file included from a.h:2:0:
b.h:1:2: error: #endif without #if
代码生成
当然,由于你的程序在词法上是合理的,但在语法上不正确,所以这个阶段永远不会被执行。
答案 3 :(得分:-2)
#if / #ifdef / #ifndef
#elif
#else
#endif
必须在一个文件中匹配。