C语言 谁定义运算符优先级和结合性,它与求值顺序有什么关系?

shyt4zoc  于 2023-10-16  发布在  其他
关注(0)|答案(6)|浏览(150)

简介

在每一本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)
令我惊讶的是,许多人告诉我,我完全错了。为了证明他们是错的,我决定转向ANSI C11标准。我再次惊讶地发现,很少提到运算符优先级和结合性。

问题

1.如果我认为函数总是从左到右求值的观点是错误的,那么关于函数优先级和结合性的表格到底意味着什么呢?
1.如果不是ANSI,谁定义运算符优先级和结合性?如果是ANSI定义的,为什么很少提到运算符优先级和结合性?运算符优先级和结合性是从ANSI C标准中推导出来的,还是在数学中定义的?

7kqas0il

7kqas0il1#

运算符优先级在相应的标准中定义。C和C的标准是C和C到底是什么的唯一真实定义。如果你仔细看,细节就在那里。事实上,细节是在语言的语法中。例如,看看C++中+-的语法产生式规则(统称为 additive-expressions):

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

正如你所看到的,multiplicative-expressionadditive-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总是在y之前计算。这允许短路,其中如果x已知是true,则不需要评估y
求值的顺序以前在C和C++中是使用 sequence points 定义的,两者都改变了术语,以 sequenced before 关系来定义事物。有关详细信息,请参阅Undefined Behaviour and Sequence Points

xriantvc

xriantvc2#

C标准中运算符的优先级由语法表示。
(C99,6.5p3)“运算符和操作数的分组由语法表示。74)”
74)“语法指定运算符在表达式计算中的优先级”
C99 Rationale还表示,
“优先级规则被编码到每个运算符的语法规则中.”

“结合性规则也同样地被编码到句法规则中.”
还请注意,结合性与求值顺序无关。于:
f1()* f2()+ f3()
函数调用以任何顺序进行评估。C语法规则说f1() * f2() + f3()表示(f1() * f2()) + f3(),但表达式中操作数的求值顺序未指定。

j8ag8udp

j8ag8udp3#

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

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();
r7s23pms

r7s23pms4#

标准中定义了优先级和结合性,它们决定了如何构建语法树。优先级根据运算符类型(1+2*31+(2*3)而不是(1+2)*3)工作,结合性根据运算符位置(1+2+3(1+2)+3而不是1+(2+3))工作。
求值的顺序是不同的-它没有定义如何构建语法树-它定义了如何在语法树中evaluate运算符的节点。求值顺序是定义的,而不是定义的--你永远不能依赖它,因为编译器可以自由选择任何他们认为合适的顺序。这样做是为了让编译器可以尝试优化代码。这个想法是程序员编写的代码不应该受到评估顺序的影响,并且无论顺序如何都会产生相同的结果。

vbopmzt1

vbopmzt15#

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

baubqpgj

baubqpgj6#

首先,你混淆了运算符优先级/结合性和求值顺序。这是两个不同的概念。

*运算符优先级告诉您哪些操作发生在哪些操作数之间。
*计算顺序告诉你这些操作数的计算顺序,在运算符之前。

总的来说,运算符优先级告诉我们发生了以下操作:

x1 = f1()
x2 = f2()
x3 = f3()
xm = x2 * x3
xa = x1 + xm

唯一的排序要求(由于依赖关系)是x2x3必须在xm之前计算,x1xm必须在xa之前计算。除此之外,编译器可以自由地按照它想要的任何顺序做事情。

运算符优先级由语言语法决定

运算符优先级是语言语法的结果。例如,C和 C++ 都有规则:

  • additive-expression*:

       * 乘法表达式 *
       * 加法表达式 * `+` * 乘法表达式 *
       * 加法表达式 * `-` * 乘法表达式 *

根据这个规则,表达式f1() + f2() * f3()被解析为:

additive-expression
                          │
           ┌──────────────┼──────────────┐
           │              │              │
   additive-expression   '+'   multiplicative-expression
           │                   ┌─────────┼─────────┐
 multiplicative-expression     │         │         │
           │                  ...       '*'       ...
          ...                  │                   │
           │            postfix-expression  postfix-expression
    postfix-expression         │                   │
           │                  f2()                f3()
          f1()
  • 注意:...表示有许多规则被扩展到postfix-expression。*

换句话说,操作数按如下方式分组:

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

优先性、arity和associativity都是语法规则的结果:

  • +优先级低于*,因为它出现在语法中的较浅级别
  • +是从左到右的关联,因为加法表达式是左递归的
  • +是一个binary运算符,因为有两个规则additive-expression和multiplicative-expression扩展到它的两侧

求值顺序由排序规则决定

如前所述,求值顺序与优先级是分开的。优先级只能告诉你乘法f2() * f3()发生了,但它不能告诉你是f2()还是f3()先执行。
[执行简介]第10页指出:
除非另有说明,否则各个运算符的操作数和各个表达式的子表达式的求值都是未排序的。
注意,[intro.execution] p11还声明函数调用不能交错,因此函数的行为就像是 * 不确定顺序的 *,而不是 * 无序的 *。换句话说,f2()f3()不能并行/交错执行,但未指定先执行f2()还是f3()
有些运营商确实有排序;例如,在f4() << f5()中,f4()总是在f5()之前执行,因为[expr.shift] p4中的顺序:
[For E1 << E2,]表达式E1在表达式E2之前排序。

相关问题