代码之家  ›  专栏  ›  技术社区  ›  P.W

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

  •  4
  • P.W  · 技术社区  · 6 年前

    编译以下程序:

    // #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 :

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

    但这并没有说明 为什么? 当条件失败时,检查词汇有效性。

    我错过什么了吗?

    3 回复  |  直到 6 年前
        1
  •  1
  •   zwol    6 年前

    “为什么(关于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当前预处理器的三分之一之后,已经有好几年了,但是我仍然忘记了很多细节)。你看,那个 起初的 评论 之间 #if 0 #结束 而不是 /* */ ,当然,这些评论有时会包含撇号。所以,当Per Bothner和Neil Booth,Chiaki Ishikawa和我替换GCC原来的“C兼容编译器预处理器”时 1 有了集成的、完全符合标准的“cpplib”,大约是gcc3.0,我们觉得我们需要在这里减少一点兼容性。


    1 如果你已经长大了,知道为什么RMS觉得这个名字很有趣,请举手。

        2
  •  2
  •   Shafik Yaghmour    6 年前

    translation phase 3 " 最后要赶上 不能是上述字符之一的非空白字符 是未定义的行为。 C11 6.4 Lexical elements p3

    在翻译的第七和第八阶段,标记是语言的最小词汇成分。这个 标记的类别有:关键字、标识符、常量、字符串文字和标点符号。 预处理标记是翻译过程中语言的最小词汇成分 . 预处理标记的类别有:头名称, 标识符、预处理数字、字符常量、字符串文字、标点符号和 在词汇上与其他预处理不匹配的单个非空白字符 代币类别(69) 如果“或”字符与最后一个类别匹配,则行为为 ....

    preprocessing-token 是:

    预处理令牌:
    标题名称
    标识符
    pp编号
    字符常数

    标点符号

    其中 在第二个例子中 non-white-space character that cannot be one of the above .

    -pedantic-errors 它甚至变成了一个错误 godbolt session . 正如rici所指出的,只有在令牌通过预处理之后,它才成为一个约束冲突。

    这个 gcc document you cite 基本上是这么说的:

    仍然运行初始 变换和 . 因此,它必须在词汇上都是有效的C。通常情况下,唯一重要的方法是 失败的条件组中的字符串文字仍必须正确结束 . ...

        3
  •  1
  •   M.M    6 年前

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

    将源文件分解为预处理标记和序列 空白字符(包括注释)。

    预处理令牌 是:

    标题名称
    标识符
    pp编号
    字符常数

    标点符号
    不能是上述字符之一的每个非空白字符

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

    ' 或者 " 字符匹配最后一个类别,行为为 未定义。

    因此,您的第二个代码在翻译阶段3会导致未定义的行为。