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

C 当条件包含为false时,为什么条件包含中的受控组在词汇上有效?,c,c-preprocessor,conditional-compilation,C,C Preprocessor,Conditional Compilation,以下程序编译: // #define WILL_COMPILE #ifdef WILL_COMPILE int i = #endif int main() { return 0; } GCC现场演示 但以下将发出警告: //#define WILL_NOT_COMPILE #ifdef WILL_NOT_COMPILE char* s = "failure #endif int main() { return 0; } GCC现场演示 我知道在第一个例子中

以下程序编译:

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

int main()
{   
    return 0;
}
GCC现场演示

但以下将发出警告:

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

int main()
{   
    return 0;
}
GCC现场演示

我知道在第一个例子中,控制组在到达时被移除。因此,它编译时不会出现错误或警告

但是,为什么在第二个例子中,当对照组不包括在内时,词汇有效性是必需的呢

我在网上搜索时发现:

即使条件失败,其内部的受控文本仍会通过初始转换和标记化运行。因此,它必须在词汇上都是有效的C。通常情况下,唯一重要的方法是,失败的条件组中的所有注释和字符串文本仍然必须正确结束

但这并没有说明当条件失败时检查词汇有效性的原因

我遗漏了什么吗?

在中,预处理器将生成预处理器标记,并且具有一个
字符,该字符最终位于不能是上述字符之一的“全部捕获”非空白字符中 是未定义的行为。 见:

在翻译阶段7和8,标记是语言的最小词汇元素 标记的类别有:关键字、标识符、常量、字符串文字和标点符号。 预处理标记是翻译中语言的最小词汇元素 阶段3到6。预处理标记的类别包括:标题名称, 标识符、预处理数字、字符常量、字符串文字、标点符号和 词汇上与其他预处理不匹配的单个非空白字符 标记类别。69)如果“或”字符与最后一个类别匹配,则行为为 未定义。

供参考的资料包括:

预处理令牌:
标题名称
标识符
pp编号
字符常数
字符串文字
标点符号 不能是上述字符之一的每个非空白字符

第二个示例中未匹配的
匹配的
非空白字符不能是上述
字符之一

由于这是未定义的行为,而不是约束,编译器没有义务对其进行诊断,但肯定允许它使用
-pedantic errors
,它甚至会变成一个错误。正如rici指出的,只有在令牌通过预处理后才成为约束冲突

这句话基本上是这么说的:

…即使条件失败,它内部的受控文本仍会通过初始转换和标记化运行。因此,它必须在词汇上都是有效的C。通常情况下,唯一重要的是,失败条件组中的所有注释和字符串文本仍必须正确结束

“为什么[关于C的某些东西]是这样的?”问题通常无法回答,因为1989年C标准的制定者都不是来回答问题的(据我所知,无论如何),如果他们在这里,那是近三十年前的事了,他们可能不记得了

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

/* this comment's perfectly fine even though it has an unclosed
   character literal inside */
还要注意的是,扫描注释的结尾非常简单。
/*
您查找下一个
*/
/
您查找的是行的结尾。唯一的复杂之处是应该首先转换三角图和反斜杠换行符。标记注释的内容将是多余的代码,毫无用处目的

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

默认情况下,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当前预处理器的三分之一才二十年,但我仍然忘记了很多细节)你看,原来的C预处理器,K和R编写的,确实允许跳过条件组中的任意无意义,因为它最初不是围绕标记的概念构建的;它将文本转换为其他文本。因此人们会在
#if 0
#endif
之间放置注释,而不是
/*
*/
,很自然,这些注释有时会包含撇号。因此,当Per Bothner、Neil Booth、Chiaki Ishikawa和我将GCC最初的“C兼容编译器预处理器”1替换为集成的、完全符合标准的“cpplib”时“,大约是GCC 3.0,我们觉得需要在这里减少一点兼容性


1如果您已经足够大,知道RMS为什么认为这个名字很有趣,请举手。

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

那个