谁定义了C运算符优先级和关联性?

介绍

在C / C ++的每本教科书中,您都可以找到运算符优先级和关联表,如下所示:

运算符优先级和关联表

http://en.cppreference.com/w/cpp/language/operator_precedence

StackOverflow上的一个问题是这样的问题:

以下函数的执行顺序如下:

f1() * f2() + f3();
f1() + f2() * f3();

参考上面的图表,我自信地回答说函数具有从左到右的关联性,因此在前面的语句中,在这两种情况下都会像这样评估:

f1() – > f2() – > f3()

评估函数后,您完成评估,如下所示:

(a1 * a2)+ a3
a1 +(a2 * a3)

令我惊讶的是,很多人告诉我,我错了。 决心certificate他们错了,我决定转向ANSI C11标准。 我再一次惊讶地发现在运算符优先级和关联性方面提到的很少。

问题

  1. 如果我认为函数总是从左到右进行评估是错误的,那么表中涉及函数优先级和关联性的真正含义是什么?
  2. 谁定义了运算符优先级和关联性,如果它不是ANSI? 如果是ANSI的定义,为什么很少提到运算符优先级和关联性? 运算符优先级和关联性是从ANSI C标准推断还是在数学中定义?

运算符优先级在适当的标准中定义。 C和C ++的标准是C和C ++究竟是什么的唯一真实定义。 所以如果仔细观察,细节就在那里。 事实上,细节是语言的语法 。 例如,看看C ++中的+-语法生成规则(统称为加法表达式 ):

 additive-expression: multiplicative-expression additive-expression + multiplicative-expression additive-expression - multiplicative-expression 

如您所见, 乘法表达式加法表达式的子规则 。 这意味着如果你有类似x + y * z东西, y * z表达式是x + y * z的子表达式。 这定义了这两个运算符之间的优先级

我们还可以看到, additive-expression的左操作数扩展为另一个additive-expression ,这意味着对于x + y + zx + y是它的子表达式。 这定义了关联性

关联性确定如何对相同运算符的相邻使用进行分组。 例如, +是从左到右的关联,这意味着x + y + z将按如下方式分组: (x + y) + z

不要将此误认为是评估顺序 。 绝对没有理由在x + y之前无法计算z的值。 重要的是计算的是x + y而不是y + z

对于函数调用操作符,从左到右的关联性意味着f()() (例如,如果f返回一个函数指针就可能发生)被分组如下: (f())() (当然,其他方向没有任何意义)。

现在让我们考虑您正在考虑的示例:

 f1() + f2() * f3() 

*运算符的优先级高于+运算符,因此表达式的分组如下:

 f1() + (f2() * f3()) 

我们甚至不必在这里考虑关联性,因为我们没有任何相同的运算符彼此相邻。

然而,对函数调用表达式的评估完全没有排序。 没有理由首先调用f3 ,然后调用f1 ,然后调用f2 。 在这种情况下,唯一的要求是在运算符之前评估运算符的操作数。 因此,这意味着必须在评估*之前调用f2f3 ,并且必须评估*必须在评估+之前调用f1

然而,一些运营商对其操作数的评估进行了排序。 例如,在x || y x || yx始终在y之前求值。 这允许短路,其中如果已知x已经为true则不需要评估y

之前在C和C ++中使用序列点定义了评估顺序 ,并且两者都改变​​了术语,以根据关系之前顺序来定义事物。 有关更多信息,请参阅未定义的行为和序列点 。

语法表示C标准中运算符的优先级。

(C99,6.5p3)“运算符和操作数的分组由语法表示.74)”

74)“语法指定运算符在表达式求值中的优先级”

C99理由也说

“优先规则被编码为每个运算符的语法规则。”

“关联性规则同样被编码为句法规则。”

另请注意,关联性与评估顺序无关。 在:

f1()* f2()+ f3()

函数调用以任何顺序进行计算。 C语法规则说f1() * f2() + f3()表示(f1() * f2()) + f3()但是未指定表达式中操作数的求值顺序。

考虑优先级和关联性的一种方法是想象语言只允许包含赋值和一个运算符的语句,而不是多个运算符。 所以声明如下:

 a = f1() * f2() + f3(); 

不允许,因为它有5个运算符:3个函数调用,乘法和加法。 在这种简化的语言中,您必须将所有内容分配给临时对象,然后将它们组合在一起:

 temp1 = f1(); temp2 = f2(); temp3 = temp1 * temp2; temp4 = f3(); a = temp3 + temp4; 

关联性和优先级指定必须以该顺序执行最后两个语句,因为乘法具有比加法更高的优先级。 但它没有指定前3个陈述的相对顺序; 做同样有效:

 temp4 = f3(); temp2 = f2(); temp1 = f1(); temp3 = temp1 * temp2; a = temp3 + temp4; 

sftrabbit给出了一个示例,其中函数调用运算符的关联性是相关的:

 a = f()(); 

当如上简化它时,这变为:

 temp = f(); a = temp(); 

标准中定义了优先级和关联性,它们决定了如何构建语法树。 优先级按运算符类型工作( 1+2*31+(2*3)而不是(1+2)*3 ),关联性由运算符位置( 1+2+3(1+2)+3不是1+(2+3) )。

评估顺序不同 – 它没有定义如何构建语法树 – 它定义了如何在语法树中evaluate运算符的节点。 评估顺序定义为不定义 – 您永远不能依赖它,因为编译器可以自由选择他们认为合适的任何顺序。 这样做是为了使编译器可以尝试优化代码。 这个想法是程序员编写的代码不应受评估顺序的影响,并且无论顺序如何都会产生相同的结果。

从左到右的关联性意味着f() - g() - h()表示(f() - g()) - h() ,仅此而已。 假设f返回1 。 假设g返回2 。 假设h返回3 。 从左到右的关联性意味着结果是(1 - 2) - 3 ,或者-4 :编译器仍然允许首先调用gh ,这与关联性无关,但是不允许给出1 - (2 - 3) ,这将是完全不同的东西。