我正在学习编译的工作方式,我的最终目标是编写一个微型C编译器。我仍在这个项目的开始。在我处理扫描器和解析器部件以构建AST的过程中,我意识到i+ +4
,i+(+4)
,i- -4
或{ {1}}。否则,例如,在i-(-4)
表达式中,i--4
被解释为一元运算符--
,并且会引发错误。我完全理解原因。这不是问题。
问题是,以前,我天真地认为,仅出于代码可读性的考虑,空格在C语言中并不那么重要。但是现在,我想知道是否还有如上所述的其他例子?
答案 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指定了翻译阶段(重新措辞和摘要,而不是确切的引号):
物理源文件多字节字符被映射到源字符集。 Trigraph序列被单字符表示代替。
以反斜杠继续的行被合并。
源文件从字符转换为预处理标记和空白字符-可以作为预处理标记的每个字符序列都转换为预处理标记,并且每个注释变为一个空格。
执行预处理(执行指令并扩展宏)。
字符常量和字符串文字中的源字符将转换为执行字符集的成员。
相邻的字符串文字是串联在一起的。
空白字符将被丢弃。 “每个预处理令牌都转换为令牌。产生的标记将在语法和语义上进行分析,并作为翻译单元进行翻译。”(引用的文本是我们认为的C编译的主要部分!)
程序被链接为可执行文件。
从根本上讲,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本身会忽略标记化阶段之后的空白,而预处理器则不会。