C语言中的短路与运算符优先级

8

我知道在C语言中的逻辑运算符采用短路规则,但我的疑问是短路规则和运算符优先级规则是否相互矛盾。请看下面的例子:

#include<stdio.h>
int main()
{
    int a;
    int b=5;

    a=0 && --b;
    printf("%d %d",a,b);

    return 0;
}

根据优先级规则,前缀运算符的优先级最高。所以应该先计算 --b,然后是 &&,最后将结果赋值给 a。因此,期望输出应为 0 4。但在这种情况下,&& 的第二个操作数实际上从未执行,结果变成了 0 5
为什么这里没有应用优先级规则?逻辑运算符是否豁免优先级规则?如果是,还有哪些运算符会显示这样的行为?这种行为背后的逻辑是什么?
5个回答

8
优先级影响模糊表达式的解析。当有多种方式解释带有多个运算符的表达式时,优先级告诉我们哪种解释是正确的。将优先级视为一种确定隐含括号位置的机制。
例如,在所讨论的语句中,有两种有效的解析方式。如果=的优先级高于&&,它可以被读作:
(a = 0) && --b;

但是由于 && 的优先级更高,它实际上被解释为:

a = (0 && --b);

(注意:您的代码格式表明这是第一个。小心不要误导!) 评估顺序不同于优先级。它们是相关但独立的概念。在优先级用于确定表达式的正确解析之后,评估顺序告诉我们要按什么顺序评估操作数。是从左到右?从右到左?同时进行?未指定?
就大多数情况而言,评估顺序未指定。像+*<<这样的运算符没有定义的评估顺序。编译器可以随意处理,程序员不能编写依赖于任何特定顺序的代码。a + b可能会先评估a然后b,或者先评估b然后a,甚至可以交织它们的评估。 =&&等是例外。 =始终从右到左进行评估,&&从左到右进行评估并具有短路功能。
以下是我们语句的逐步评估过程:
  1. a = (0 && --b)= 从右向左计算
    1. 0 && --b&& 左到右计算,使用短路规则
      1. 0,判断为假,触发短路规则并取消下一步操作
      2. --b,由于短路规则未执行
      3. 结果为 0
    2. a,变量引用计算
    3. a = 0,进行赋值操作,整个结果为 0

你说过 +* 没有特定的顺序,但是 this table 显示顺序为从左到右。为什么?

该表格的最后一列为结合性。当我们两次使用相同的运算符或使用具有相同优先级的运算符时,结合性会打破优先级关系。

例如,我们应该如何读取 a / b / c。它是:(a / b) / c 还是 a / (b / c)
根据表格,/ 具有从左到右的结合性,因此是第一个选项。
那么像 foo = bar = baz 这样的链式赋值怎么办?现在,赋值具有从右到左的结合性,因此正确的解析是 foo = (bar = baz)
如果这一切变得混乱,请专注于一个简单的经验法则:
“优先级和结合性与评估顺序无关。”

大多数情况下,评估顺序未指定。像 +*<< 这样的运算符没有定义的评估顺序。编译器可以随意处理,程序员不得编写依赖于任何特定顺序的代码。a + b 可以先计算 a 再计算 b,也可以先计算 b 再计算 a,甚至可以交织它们的计算。因此,为了安全起见并确保编译器按照所需的方式计算表达式,括号是一个神奇的解决方法吗?例如 (a) + (b)。或者是否存在即使使用括号也不会影响编译器特定评估的情况? - RobertS supports Monica Cellio
括号不影响计算顺序。加上它们也没有帮助。将它们放在单独的语句中,或使用像,(逗号运算符)、&&||这样的运算符。 - John Kugelman
除了逻辑运算符之外,还有哪些运算符遵循短路规则?你说过 +* 没有特定的顺序,但是这个表格:https://en.cppreference.com/w/c/language/operator_precedence 显示它们的顺序是从左到右,为什么会这样呢? - LocalHost
非常感谢您,先生。您最后一行解决了我所有的疑问。 - LocalHost

8
你混淆了两个相关但不同的主题: 操作符优先级求值顺序
操作符优先级规则决定不同运算符之间如何分组。在这个表达式的情况下:
 a=0 && --b;

运算符按如下方式分组:
 a = (0 && (--b));

然而,这对操作数的评估顺序没有任何影响。特别是双与(&&)运算符规定先评估左侧操作数,如果它的值为0,则不会评估右侧操作数。

因此,在这种情况下,将首先评估&&的左侧,即为0,因为它的值为0,所以将不会评估右侧,即--b,因此b不会增加。

以下是运算符优先级和评估顺序之间差异的另一个示例。

int val()
{
    static x = 2;
    x *= 2;
    return x;
}

int main()
{
    int result = val() + (5 * val());
    printf("%d\n", result);
    return 0;
}

上述程序将打印什么?事实证明,有两种可能性,都是有效的。

在这个表达式中:

val() + (5 * val())

没有运算符具有任何类型的短路行为。 因此,编译器可以自由地以任何顺序评估 + * 的各个操作数。

如果首先评估 val()的第一个实例,则结果将是 4 +(5 * 8)== 44 。 如果先评估 val()的第二个实例,则结果将是 8 +(5 * 4)== 28 。 同样,两者都是有效的,因为可以按任何顺序评估操作数。


1
@Noshiii 这些规则仅规定操作数如何分组,而不是它们何时被评估。除了 ||&& 之外,函数调用的参数评估在实际调用之前被排序,尽管参数可以按任意顺序进行评估。 - dbush
1
@Noshiii C标准描述了各个运算符的工作方式。但在大多数情况下,操作数的求值顺序是未指定的,正如我的示例所示。为了更简单地说明上述问题,val() - val()可以是4-4 - dbush
1
@Noshiii 不仅与编译器相关,而且顺序可能会因为微小的代码更改或使用不同选项重新编译而发生变化。 - dbush
1
@Noshiii 括号只是强制分组。c 可能会在 a / b 之前被计算。 - dbush
1
@Noshiii:同样,在算术表达式中,分组和优先级并不控制子表达式计算的顺序。对于(a/b)*c,每个abc都可以按任意顺序进行评估。在并行系统上,它们可以同时被评估。重要的是a/b结果乘以c结果,但这些结果获得的确切顺序没有指定或固定。甚至在同一个程序中也不必保持一致。 - John Bode
显示剩余8条评论

0

operator && 的求值顺序是从左到右。

= 的优先级较低,实际上只有 operator , 的优先级比 = 更低。

因此,表达式将首先读取 a = (0 && --b),给定所述的求值顺序,0 先进行求值。

由于 0 求值为 false,因此无需评估表达式的第二部分,因为 false && truefalse,鉴于表达式的第一部分是 false,表达式将始终为 false

如果您使用了 || 运算符,则必须评估表达式的第二部分。


是的,我明白了,但它仍然有三个不同的运算符。如果这里没有逻辑运算符,我们将使用优先级表来解决这个问题。但现在我们不这样做吗? - LocalHost
1
@Noshiii,我的回答有些混淆,我已经更正了,希望我没有让你更加困惑。 - anastaciu
1
“=”运算符没有“从右到左的求值顺序”。与C语言中除了&&||,?:之外的所有运算符一样,它可以以任何顺序评估其操作数,可能同时进行评估。 - Cubbi
@Cubbi,没错,是我的错误。有趣的是,昨天我回答了一个与此相关的问题(https://dev59.com/Y7voa4cB1Zd3GeqP0EGX#62051230)。 - anastaciu

0

运算符优先级并不一定意味着表达式会首先被执行,它只是表示表达式被解析为高优先级操作的结果用于低优先级操作而不是相反。实际的表达式只有在需要时才会被评估!


-2

运算符优先级并不是游戏中的全部。还有评估顺序。这就要求首先评估a=0(评估顺序从左到右),然后根本不评估&&后面的右部分。

这就是C语言的工作方式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接