编译器如何知道函数调用中的逗号不是逗号运算符?
Posted
技术标签:
【中文标题】编译器如何知道函数调用中的逗号不是逗号运算符?【英文标题】:How does the compiler know that the comma in a function call is not a comma operator? 【发布时间】:2013-06-27 08:03:37 【问题描述】:考虑函数调用(调用int sum(int, int)
)
printf("%d", sum(a,b));
编译器如何确定函数调用sum(int, int)
中使用的,
不是逗号运算符?
注意:我不想在函数调用中实际使用逗号运算符。我只是想知道编译器如何知道它不是逗号运算符。
【问题讨论】:
你说的是两个逗号中的哪一个... 为什么人们投票结束这个!!!!!!!!! 不同意这个问题是题外话。该问题询问了有关实现如何解释某种语法的微妙细节,并且可以通过引用相关的标准引号来最终回答。 尝试解决问题的努力不适用于此处。理解或寻找标准语录并不是一件容易的事。 有两个函数调用,一个到sum
,一个到printf
。
我曾经有一些 C 代码表现得很奇怪,因为我正在通过指针进行整数除法。即,表达式为a/*b
。通过添加一些空格来修复它:a / *b
【参考方案1】:
现有答案说“因为 C 语言规范说它是列表分隔符,而不是运算符”。
但是,您的问题是问“编译器如何知道...”,这完全不同:这与编译器知道printf("Hello, world\n");
中的逗号不是逗号运算符的方式实际上没有什么不同:编译器“知道”是因为逗号出现的上下文 - 基本上,之前发生了什么。
C“语言”可以在Backus-Naur Form (BNF) 中进行描述——本质上,是编译器的parser 用来扫描您的输入文件的一组规则。 C 的 BNF 将区分语言中这些不同可能出现的逗号。
有很多关于编译器如何工作的好资源,还有how to write one。
【讨论】:
问题是关于 C 的。在 C++ 的上下文中谈论“解析器”是不必要的复杂化。 @anatolyg - 已修复。我现在脑子里肯定有 C++。【参考方案2】:查看 C 语言的语法。它在standard 的附录 A 中完整列出。它的工作方式是,您可以逐步遍历 C 程序中的每个标记,并将它们与语法中的下一项匹配。在每个步骤中,您只有有限数量的选项,因此任何给定字符的解释将取决于 它出现的上下文。在语法中的每条规则中,每一行都为程序提供了一个有效的替代方案来匹配。
具体来说,如果您查找parameter-list
,您会看到它包含一个明确的逗号。因此,每当编译器的 C 解析器处于“参数列表”模式时,它找到的逗号将被理解为 参数分隔符,而不是 逗号运算符。括号也是如此(也可以出现在表达式中)。
这是因为parameter-list
规则谨慎使用assignment-expression
规则,而不仅仅是简单的expression
规则。 expression
可以包含逗号,而assignment-expression
不能。如果不是这种情况,语法将是模棱两可的,编译器在遇到参数列表中的逗号时将不知道该怎么做。
但是,一个左括号,例如,不是函数定义/调用的一部分,或if
、while
或for
语句,将被解释为表达式的一部分(因为没有其他选项,但前提是表达式的开头是该点的有效选择),然后,在括号内,expression
语法规则将适用,并且允许逗号运算符。
【讨论】:
我忘记了有个技术术语叫这个名字。我只是说任何给定的标记只能在它出现的上下文中被理解。换句话说,我使用“上下文敏感”作为形容词而不是名词。但是,我怀疑唯一对此感到困惑的人是已经知道答案的人! 这是一个很好的答案,但您还应该提到 逗号之间的内容 是assignment-expression
非终结符而不是 expression
非终结符(如 Jens 的回答中所述) ,因此不允许parameter-list
顶层的,
成为逗号运算符。如果标准按照您所描述的那样做而没有这样做,那么整体语法就会模棱两可。
@Zack,确实如此。我已经用该信息扩展了答案。
@EricLippert:我认为说 C 具有上下文无关语法是没有意义的。如果你朝那个方向走,那么你也可以声称 C++ 有一个 CFG(因为就像在 C 的情况下一样,它是模棱两可的,并且需要语义传递来拒绝无效程序)。如果您想非常严格,那么您还可以声称大多数编程语言没有具有 CFG,因为它们都需要在程序被认为有效之前定义之前的声明,这不是上下文无关的。两者都不是一个非常有用的定义,因为它将大多数语言归为同一类别。 (续)
@EricLippert: (cont'd) ...从实际的角度来看(在理论方面可能不是那么多)我觉得一个有用的定义是 C 是上下文无关的,如果它有CFG 明确解析所有有效的 C 程序假设没有未声明的标识符。但在这种情况下,C 不是上下文无关的(因此没有 CFG),因为经典的 T * T;
歧义,这需要知道 T
是 是什么(而不仅仅是它是否被声明) .因此,我认为说 C 是上下文无关的是没有意义的。【参考方案3】:
原因是 C 语法。虽然其他人似乎都喜欢引用这个例子,但真正的问题是标准 (C99) 中函数调用的短语结构语法。是的,函数调用由应用于后缀表达式(例如标识符)的()
运算符组成:
6.5.2 postfix-expression:
...
postfix-expression ( argument-expression-list_opt )
一起
argument-expression-list:
assignment-expression
argument-expression-list , assignment-expression <-- arglist comma
expression:
assignment-expression
expression , assignment-expression <-- comma operator
逗号运算符只能出现在表达式中,即在语法中更靠后的位置。因此编译器将函数参数列表中的逗号视为分隔 assignment-expressions 的分隔符,而不是分隔 表达式 的分隔符。
【讨论】:
@hacks:条件表达式或一元表达式,后跟赋值运算符,后跟赋值表达式。 我没明白你的意思,请详细说明。应该不胜感激 扩展一点@Jens 的回答:让我们改变问题并简化它。让我们用高尔夫球(涂成黄色)和透明的大塑料球代替“表情”,这些塑料球可以打开,里面有东西:(
stuff )
。语法实际上是说你可能有黄色的高尔夫球,它们会自动分开。或者,您可以提供一个清晰的球只要您使用了两半。透明球作为一个单元工作,它不能打开和分离。所以: f( (a,b), g ) 有一个“透明球” (a,b) 和一个“黄色球” g,因此正好有两个球,呃,论据。
我用完了评论室,所以,继续,回到真正的 C 语法:括号允许您转义到“完整的”表达式,其中逗号是逗号表达式部分。但是,除非您有一个“额外的”左括号,否则您将处于这个更有限的“赋值表达式”子语法中(如“黄色高尔夫球”的想法),其中根本不允许使用逗号。如果解析器在此上下文中遇到逗号,它必须停止并完成赋值表达式。这是因为(
以)
“结束”:括号结束了完整的表达式上下文。
嗯,我没有任何其他自然语言可以表达这一点。考虑
…
、[
…]
和(
…)
。他们“匹配”:如果你写a[3
,那显然是错误的。如果你写a[(3]
,那显然仍然是错误的。 (
仅由匹配的 )
结束。这“关闭”了整个序列,清楚地表明了什么与什么相配。【参考方案4】:
这个问题有多个方面。一个标准是定义是这样说的。那么,编译器如何知道这个逗号在什么上下文中呢?这就是解析器的工作。特别是对于 C,该语言可以由 LR(1) 解析器 (http://en.wikipedia.org/wiki/Canonical_LR_parser) 解析。
它的工作方式是解析器生成一组表,这些表构成了解析器的可能状态。只有特定的一组符号在特定的状态下是有效的,而这些符号在不同的状态下可能有不同的含义。由于前面的符号,解析器知道它正在解析一个函数。因此,它知道可能的状态不包括逗号运算符。
我在这里非常笼统,但您可以阅读 Wiki 中的所有详细信息。
【讨论】:
【参考方案5】:从 C99 6.5.17 开始:
如语法所示,逗号运算符(如本小节所述)不能 出现在使用逗号分隔列表中的项目的上下文中(例如函数或列表的参数 初始化程序)。另一方面,它可以用在带括号的表达式中或第二个表达式中 在这种情况下条件运算符的表达式。在函数调用中
f(a, (t=3, t+2), c)
该函数有三个参数,第二个的值为 5。
另一个类似的例子是数组或结构的初始化列表:
int array[5] = 1, 2;
struct Foo bar = 1, 2;
如果要使用逗号运算符作为函数参数,请像这样使用它:
sum((a,b))
当然,这不会编译。
【讨论】:
正确但不是问题的答案。 @Yu :我不想使用逗号运算符。我只想知道编译器是怎么知道它不是逗号操作符的! @sasha.sochka 查看 OP 的评论。他想知道解析器是如何工作的,而不是如何在函数调用中使用逗号。 @hacks 知道了,编辑了我的话。使用逗号运算符作为函数参数实际上并没有什么用,但知道如何使用它仍然很有趣,所以我将保留这部分。 @YuHao;多谢,伙计!至少。也感谢您编辑我的帖子。【参考方案6】:The draft C99 standard says:
如语法所示,逗号运算符(如本小节所述)不能 出现在使用逗号分隔列表中的项目的上下文中(例如函数的参数或初始化程序列表)。另一方面,在这种情况下,它可以用在带括号的表达式中或条件运算符的第二个表达式中。在函数调用
f(a, (t=3, t+2), c)
中,该函数具有三个参数,其中第二个参数的值为 5。
换句话说,“因为”。
【讨论】:
我的孩子们不认为这是一个答案,为什么 OP 应该......但这就是原因,因为模棱两可的情况是被禁止的。以上是关于编译器如何知道函数调用中的逗号不是逗号运算符?的主要内容,如果未能解决你的问题,请参考以下文章