包含文件中的#endif可用于关闭包含文件中的#if吗?

时间:2015-09-29 15:04:37

标签: c language-lawyer

我们说我有两个文件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在拒绝我的代码方面不合规吗?

4 个答案:

答案 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预处理器在概念上执行几个阶段。

  1. 词法分析 - 将构成预处理翻译单元的字符序列分组为预处理器语言中具有已识别含义(标记)的字符串。
  2. 句法分析 - 将预处理翻译单元的标记分组为根据预处理语言语法构建的语法结构。
  3. 代码生成 - 将构成预处理翻译单元的所有文件转换为仅包含“纯”C指令的单个文件。
  4. 严格地说,C Standard (ISO/IEC 9899:201x)的§5.1.1.2中提到的与预处理相关的翻译阶段是阶段3和阶段4.阶段3几乎完全对应于词法​​分析,而阶段4则是关于代码生成。

    该图片似乎缺少句法分析(解析)。实际上,C预处理器语法非常简单,真正的预处理器/编译器与词法分析一起执行它。

    如果句法分析阶段成功结束 - 即预处理翻译单元中的所有语句根据预处理程序语法是合法的 - 可以进行代码生成并执行所有预处理指令。
    执行预处理指令意味着根据文件的语义转换源文件,然后从源文件中删除指令。
    每个预处理程序指令的语义在C标准的§6.10.1-6.10.9中指定。

    返回您的示例程序,您提供的2个文件,即a.hb.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
必须在一个文件中匹配