当条件为假时,为什么条件包含中的受控组在词汇上有效?

Why should the controlled group in a conditional inclusion be lexically valid when the conditional is false?

编译以下程序:

// #define WILL_COMPILE 
#ifdef WILL_COMPILE
int i = 
#endif

int main()
{   
    return 0;
}

GCC 现场演示 here

但是下面会发出警告:

//#define WILL_NOT_COMPILE
#ifdef WILL_NOT_COMPILE
char* s = "failure
#endif

int main()
{   
    return 0;
}

GCC 现场演示 here

我知道在第一个示例中,受控组在达到 compilation phase of the translation 时被删除。因此它编译时没有错误或警告。

但为什么第二个例子中不包括受控组时需要词汇有效性?

在线搜索我发现了这个quote

Even if a conditional fails, the controlled text inside it is still run through initial transformations and tokenization. Therefore, it must all be lexically valid C. Normally the only way this matters is that all comments and string literals inside a failing conditional group must still be properly ended.

但这并没有说明为什么在条件失败时检查词法有效性。

我是不是漏掉了什么?

translation phase 3 中,预处理器将生成预处理器令牌,并且 " 最终会捕获所有 non-white-space 字符以上之一 是未定义的行为。 见 C11 6.4 Lexical elements p3:

A token is the minimal lexical element of the language in translation phases 7 and 8. The categories of tokens are: keywords, identifiers, constants, string literals, and punctuators. A preprocessing token is the minimal lexical element of the language in translation phases 3 through 6. The categories of preprocessing tokens are: header names, identifiers, preprocessing numbers, character constants, string literals, punctuators, and single non-white-space characters that do not lexically match the other preprocessing token categories.69) If a ' or a " character matches the last category, the behavior is undefined. ....

参考 preprocessing-token 是:

preprocessing-token:
header-name
identifier
pp-number
character-constant
string-literal
punctuator
each non-white-space character that cannot be one of the above

其中第二个例子中不匹配的 " 匹配 non-white-space character that cannot be one of the above.

由于这是未定义的行为而不是约束,因此编译器没有义务对其进行诊断,但当然允许并使用 -pedantic-errors 它甚至会成为错误 godbolt session。正如 rici 指出的那样,如果令牌在预处理中幸存下来,它只会成为约束违规。

gcc document you cite 基本上说的是同一件事:

... Even if a conditional fails, the controlled text inside it is still run through initial transformations and tokenization. Therefore, it must all be lexically valid C. Normally the only way this matters is that all comments and string literals inside a failing conditional group must still be properly ended. ...

"Why is [something about C] the way it is?" 问题通常无法回答,因为 none 编写 1989 C 标准的人在这里回答问题 [据我所知,无论如何] 如果他们在这里,那是将近三十年前的事了,他们可能不记得了。

但是,我可以想到一个合理的原因,为什么跳过的条件组的内容需要包含一个有效的预处理标记序列。请注意,注释 不需要 来包含有效的预处理标记序列:

/* this comment's perfectly fine even though it has an unclosed
   character literal inside */

还请注意,扫描评论的结尾非常简单。 /*你找下一个*///你找行尾。唯一的麻烦是应该首先转换三字母和反斜杠换行符。标记评论的内容将是没有用处的额外代码。

相比之下,扫描跳过的条件组的结尾并不简单,因为条件组是嵌套的。您必须寻找 #if#ifdef#ifndef 以及 #else#endif,并计算您的深度。所有这些指令都是根据预处理器标记在词法上定义的,因为当您在跳过的条件组中 not 时,这是查找它们的最自然方式。要求跳过的条件组可以标记化允许预处理器使用相同的代码来处理跳过的条件组中的指令,就像它在其他地方所做的那样。

默认情况下,当 GCC 在跳过的条件组中遇到不可标记的行时,GCC 只会发出警告,其他地方会出现错误:

#if 0
"foo
#endif
"bar

给我

test.c:2:1: warning: missing terminating " character
"foo
^
test.c:4:1: error: missing terminating " character
"bar
^~~~

这是我故意宽容的,可能是我自己介绍的(自从我写了 GCC 当前预处理器的三分之一以来才 20 年,但我仍然忘记了很多细节)。你看,原始的 C 预处理器,K 和 R 写的,did 允许在跳过的条件组中任意废话,因为它不是构建的首先围绕代币的概念;它将文本转换为其他文本。所以人们会把 comments 放在 #if 0#endif 之间,而不是 /**/ 之间,很自然地,这些评论有时会包含撇号.因此,当我和 Per Bothner、Neil Booth 以及 Chiaki Ishikawa 将 GCC 的原始 "C-Compatible Compiler Preprocessor"1 替换为集成的、完全符合标准的 "cpplib"、大约 GCC 3.0 时,我们觉得我们需要在这里减少一点兼容性。


1如果你知道为什么 RMS 认为这个名字很有趣,请举手。

翻译阶段 3 (C11 5.1.1.2/3) 的描述,发生在预处理指令执行之前:

The source file is decomposed into preprocessing tokens and sequences of white-space characters (including comments).

preprocessing-token 的语法是:

header-name
identifier
pp-number
character-constant
string-literal
punctuator
each non-white-space character that cannot be one of the above

请特别注意,字符串文字 是单个预处理标记。随后的描述(C11 6.4/3)阐明:

If a ' or a " character matches the last category, the behavior is undefined.

所以你的第二个代码在翻译阶段 3 导致了未定义的行为。