如果我在变量旁边有一个减号,例如:
int a;
int b = -a; // UNARY OPERATOR
b = - a; // UNARY OPERATOR
'a' 之前的减号被视为一元运算符,并取 a 的负值。然而,在这个:
int a, b;
a -b; // SUBTRACTION
a - b; // SUBTRACTION
因此我由此推断:
- 运算符和操作数之间是否有空格隔开是无关紧要的。
- 判断它是减法还是一元运算符取决于前一个操作数的存在,并且与上下文高度相关。
有人可以简单总结一下编译器如何决定这一点的规则吗?
多门计算机科学课程都涉及解析和形式语言的理论,因此 Stack Overflow 答案中只能提供一点介绍。编译器可能会使用LALR 解析器。
简单来说,在编译的语言中可能出现一系列模式。例如,在 C 语言中,加法表达式是以下之一:
+
乘法表达式-
乘法表达式规则中的每个项目可能是另一个模式的标记,例如乘法表达式,或终端符号,例如
+
或int
。当编译器读取源代码时,它会尝试将看到的文本与模式进行匹配。当找到合适的匹配项时,它会将匹配的项简化
a
-
b
为与模式匹配的标记。因此,它将简化为加法表达式,并且永远不会与一元表达式模式匹配。而之前-
b
没有另一个加法表达式时,它将简化为一元表达式,并且永远不会与加法表达式模式匹配。再次强调,这只是部分流程的简化视图。在编译器开始处理语法中的标记之前,它会执行词汇分析,将字符聚类为语法标记。因此,转换
int foo;
为“关键字int
”和“标识符foo
”以及转换a---b
为“标识符a
”、“运算符--
”、“运算符-
”和“标识符b
”发生在更早/更低的分析级别。而且,至少从概念上讲,还有一个单独的预处理器级别。此外,编译器实际上并没有列出模式,它不会直接将输入序列与模式匹配。相反,语法规则用于构造一个实现该模式匹配的概念“机器”。该机器称为解析器,它内置于编译器中。每个新的输入标记都会改变机器的状态,并且机器的设计方式是使状态与模式的识别相对应。
形式数学展示了如何使用这种形式语法来构建执行解析的“机器”(计算的数学模型)。要完全理解这一点,应该学习离散数学、有限状态机和自动机、形式语言(解析理论)和编译器构造等课程。
编译的第一步是使用文本预处理器。预处理器将输入文本拆分为标记,如果标记与宏名称匹配,则执行标记替换,直到没有更多标记可替换。重要的是,预处理器是贪婪的,即在提取标记时,它会尝试提取最大可能长度的标记。字符串文字之外的空格符号始终充当标记分隔符。
a+b
被标记为a
+
b
a++b
被标记为a
++
b
(贪婪!)a+ +b
被标记为a
+
+
b
(空格相关!)a+++b
被标记为a
++
+
b
(再次贪婪!)接下来,编译器会尝试根据上下文找出标记序列的含义。如果
+
作为(子)表达式的第一个标记出现,则假定它是一元运算符,否则假定它是二进制+
。下一个标记被认为是下一个子表达式的第一个标记。关于问题的预处理器部分:
表达式如何出现是在预处理过程中早期决定的,如 C23 6.4.1 中所述,其中还包含术语“空白”的正式定义:
接下来是 C 程序员非正式地称之为“最大 munch 规则”:
用简单的英语来说:在预处理过程中,预处理器会遍历一段文本(从左到右),并尝试形成最长的字符序列,以形成各种提到的项目之一,例如您示例中的标识符
a
和。但目前也是如此,因为它在预处理过程中还没有特殊含义。当稍后转换为标记时,被归类为语言关键字。b
int
int
=
和-
是6.4.7 中定义;
的标点符号。例如-=
是有效的标点符号,因此如果它出现在 这样的表达式中a-=b
,则“最大多规则”将读取-=
为一个预处理器标记。=-
然而 本身并不是有效的标点符号,因此它会被读取为两个不同的标点符号:=
和-
。所有这些之间可能存在空格,用于分隔预处理器标记,但随后被丢弃。
这就是所有代码被解析为预处理器标记的方式。稍后在编译过程中,当项目被翻译成标记时,它们开始根据语言语法保留含义。这发生在“翻译阶段 7”,5.2.1.2: