编译期间在C中何时需要空格(或括号)?

时间:2019-03-31 10:29:23

标签: c parsing compiler-construction flex-lexer

我正在学习编译的工作方式,我的最终目标是编写一个微型C编译器。我仍在这个项目的开始。在我处理扫描器和解析器部件以构建AST的过程中,我意识到i+ +4i+(+4)i- -4或{ {1}}。否则,例如,在i-(-4)表达式中,i--4被解释为一元运算符--,并且会引发错误。我完全理解原因。这不是问题。
问题是,以前,我天真地认为,仅出于代码可读性的考虑,空格在C语言中并不那么重要。但是现在,我想知道是否还有如上所述的其他例子?

5 个答案:

答案 0 :(得分:2)

我必须修复一些旧代码并进行更改

#define ALT_7      (0xfe+OFFSET)

#define ALT_7      (0xfe +OFFSET)

原因是0xfe+OFFSET是一个预处理数字令牌,而不是人们可能天真的认为的三个令牌。旧的编译器将其解析为三,但是新的编译器失败,因为它将其解析为无效的数字常量。

在预处理器方面可能还有更多,但它更加晦涩(作为C / C ++预处理的整个主题)。

答案 1 :(得分:2)

在许多情况下确实需要空格:

  • 宏定义:

    #define MACRO (a)      // defines a simple macro, it expands to (a)
    #define MACRO(a)       // defines a function-like macro with a single parameter a
    
  • 注释语法陷阱:

    a/*b    // starts a comment */
    a/ *b   // a divided by the value pointed to by b
    
  • 预处理数字文字:

    0x2e+1  <>  0x2e +1
    
  • 三部字母的相似问题:

    "??/??/????"  <>  "??" "/??" "/????"  // "??/??/????" is parsed as "\\????"   
    
  • 令牌分离:

    a+ +b   <>  a++b   // a++b would be a syntax error
    a- -b   <>  a--b   // a--b would be a syntax error
    a& &b   <>  a&&b   // but &b is unlikely to be a valid operand for &
    
  • C ++嵌套模板问题:

    template a<b<c> >
    

答案 2 :(得分:1)

未明确指定C中何时需要空格的规则,但这些规则是C解析方式的结果。规则很复杂,因为它们涉及分析的多个阶段以及各种情况的一些例外。如果要编写C编译器,则需要使用C标准作为参考。

C 2018 5.1.1.2指定了翻译阶段(重新措辞和摘要,而不是确切的引号):

  1. 物理源文件多字节字符被映射到源字符集。 Trigraph序列被单字符表示代替。

  2. 以反斜杠继续的行被合并。

  3. 源文件从字符转换为预处理标记和空白字符-可以作为预处理标记的每个字符序列都转换为预处理标记,并且每个注释变为一个空格。

  4. 执行预处理(执行指令并扩展宏)。

  5. 字符常量和字符串文字中的源字符将转换为执行字符集的成员。

  6. 相邻的字符串文字是串联在一起的。

  7. 空白字符将被丢弃。 “每个预处理令牌都转换为令牌。产生的标记将在语法和语义上进行分析,并作为翻译单元进行翻译。”(引用的文本是我们认为的C编译的主要部分!)

  8. 程序被链接为可执行文件。

从根本上讲,C源代码中需要空格的位置受阶段3的控制,即预处理令牌的形成。这在C 2018 6.4中指定。第1段给出了预处理令牌的语法(下文有更多说明),第4段告诉我们:

  

如果输入流已解析为最多给定字符的预处理令牌,则下一个预处理令牌是可以构成预处理令牌的最长字符序列。此规则有一个例外:标头名称预处理令牌仅在#include预处理指令内以及#pragma指令内的实现定义的位置中被识别。在这种情况下,可以将标头名称或字符串文字的字符序列识别为前者。

第1段告诉我们预处理令牌是标头名称标识符 pp-number 字符-常量字符串文字标点符号或不是上述项目之一的非空格字符。

然后,第6.4节进一步介绍了这些令牌的外观。

第3阶段针对您需要空间的地方引入了两个规则:

  • 如果根据上述规则将源代码解析为一个预处理令牌,则需要两个令牌,那么必须在第一个令牌结束的位置插入一个空格。
  • 如果使用/以外的*/*来发表评论,请在它们之间留一个空格。

第4阶段产生另一个规则。因为6.10.3 3说“在类似对象的宏的定义中,标识符和替换列表之间应该有空白”,所以您需要一个空格来区分类似函数的宏:

#define foo(x) (3*(x)) // Macro that acts on argument x.
#define foo (x)        // Macro that expands to `(x)`.

答案 3 :(得分:0)

大多数语言中的词法器都是基于贪婪的正则表达式-令牌的长度尽可能长。

如果++可以解释为++运算符(从左到右),则不会被词法化为两个加号。如果inta可以解释为标识符,则不会解释为int后跟a等。

i+ +4需要空格,括号或++之间的内容,否则词法分析器将以++从左到右贪婪地消耗它。

答案 4 :(得分:0)

C编译器有几个部分,问题是:您要实现哪个?

C预处理器实际上为空白生成一个令牌,并使用该令牌来确定事物。如果您要实现组合的预处理器/编译器,则可能只需要进行一次令牌化,然后丢弃空白令牌,然后再将令牌流交给适当的编译器即可。

C本身似乎主要关心空格,制表符和换行符,以此作为令牌结束的指示。

除此之外,它还具有一个或两个字符的运算符的概念,并且似乎在贪婪地匹配它们。也就是说,- -变成了MINUS_TOKEN, MINUS_TOKEN,而--不管在哪里,总是变成了DECREMENT

也就是说,您的i--4示例给出了一个解析器错误,因为后缀减量运算符后面有一个多余的4

因此证明了运算符是贪婪地匹配的。编写i - -4 OTOH是可行的,因为贪婪的匹配将空格视为第一个-令牌的结尾,并开始一个新令牌,然后产生第二个负号。

总而言之,C本身会忽略标记化阶段之后的空白,而预处理器则不会。