【问题标题】:Operator associativity with 'postfix decrement' and 'logical AND' operators in c运算符与 c 中的“后缀递减”和“逻辑与”运算符的关联性
【发布时间】:2019-03-15 19:23:20
【问题描述】:

免责声明:我不会这样编码,我只是想了解 c 语言的工作原理!!!

输出为 12。

这个表达式(a-- == 10 && a-- == 9) 从左到右求值,在a-- == 10 处a 仍为10,但在a-- == 9 处a 为9。

1) 关于后增量评估的时间是否有明确的规则?从这个例子来看,它似乎在 && 之前但在 == 之后进行评估。是不是因为 && 逻辑运算符使a-- == 10 成为一个完整的表达式,所以 a 在执行后更新?

2) 同样对于 c/c++,某些运算符(例如前缀递减)从右到左发生,因此 a == --a 首先将 a 递减为 9,然后比较 9 == 9。c/c++ 设计这个是否有原因大大地?我知道对于 Java,情况正好相反(从左到右计算)。

#include <stdio.h>

int main() {
    int a = 10;
    if (a-- == 10 && a-- == 9)
        printf("1");
    a = 10;
    if (a == --a)
        printf("2");
    return 0;
}

【问题讨论】:

  • 这不是逻辑与,而是按位。逻辑与是&amp;&amp;
  • 递增和递减的副作用在序列点完成,而不是之前。 &amp;&amp;|| 运算符提供序列点; &amp;| 运算符没有。
  • 不!这个问题对于未定义行为的问题不是“重复的”。没有任何 UB,因为 C 标准说 logical and 运算符 "guarantees left-to-right evaluation; there is a sequence point after the evaluation of the first operand."。因此,a-- == 10 的所有副作用在 a-- == 9 评估之前完成。这是喷气式飞机的另一种情况。
  • @Observe:两者都不是由编译器首先编译的。在现代编译器中,表达式被解析并转换为内部表示,这可能是某种丰富的数据结构。该数据结构由编译器中的各种代码转换,包括优化,其结果通常是不可预测的。然后将结果用于生成一些内部代码,然后将其用于生成汇编代码。假设表达式的可识别部分仍然存在,它的任何部分都可以在汇编代码中首先出现。

标签: c operator-precedence post-increment side-effects sequence-points


【解决方案1】:

这个表达式 (a-- == 10 && a-- == 9) 从左到右计算,

是的,主要是因为&amp;&amp; 很特别。

a 在 a-- == 10 处仍然是 10

是的,因为 a-- 会产生旧值。

但是对于 a-- == 9,a 是 9。

是的,因为&amp;&amp; 处的序列点保证在评估 RHS 之前完成对a 值的更新。

1) 对于后增量评估的时间是否有明确的规则?

我认为最好的答案是“不”。 ++-- 的副作用在下一个序列点之前的某个时间点完成,但除此之外,你不能说。对于定义明确的表达式,副作用何时完成并不重要。如果一个表达式对副作用何时完成很敏感,这通常意味着该表达式是未定义的。

从这个例子来看,它似乎在 && 之前但在 == 之后进行评估。是不是因为 && 逻辑运算符使 a-- == 10 成为一个完整的表达式,所以 a 在执行后更新?

基本上是的。

2) 同样对于 c/c++,某些运算符(例如前缀递减)从右到左发生

小心。我不确定你的意思,但不管它是什么,我几乎可以肯定它不是真的。

所以 a == --a 首先将 a 递减到 9,然后比较 9 == 9。

不,a == --a 未定义。不知道它是做什么的。

c/c++ 被这样设计有什么原因吗?

是的。

我知道对于 Java,情况正好相反(从左到右计算)。

是的,Java 不同。


这里有一些指南可以帮助您理解 C 表达式的求值:

  1. 了解运算符优先级和关联性的规则。对于“简单”表达式,这些规则几乎可以告诉您有关表达式求值的所有信息。给定a + b * cb 乘以c,然后将乘积添加到a,因为* 的优先级高于+。给定a + b + ca 被添加到b,然后总和被添加到c,因为+ 从左到右关联。

  2. 除了关联性(如第 1 点所述)外,尽量不要使用“从左到右”或“从右到左”评估。 C 没有从左到右或从右到左的评估。 (显然 Java 是不同的。)

  3. 棘手的地方是副作用。 (当我在第 1 点说“简单”表达式时,我的意思是“没有副作用的表达式”。)副作用包括(a)函数调用,(b)= 的赋值,(c)@987654340 的赋值@、-= 等,当然还有 (d) 使用 ++-- 递增/递减。 (如果从变量 fetch 很重要,这通常仅适用于限定为 volatile 的变量,我们可以将 (e) 从 volatile 变量中提取添加到列表中。)一般来说,你无法判断副作用何时发生。尽量不在乎。只要您不关心(只要您的程序对副作用的重要性顺序不敏感),就没有关系。但是如果你的程序敏感的,它可能是未定义的。 (见下文第 4 点和第 5 点的更多内容。)

  4. 在试图改变同一个变量的同一个表达式中永远不能有两个副作用。 (例如:i = i++a++ + a++。)如果这样做,则表达式未定义。

  5. 对于一类异常,您绝不能产生副作用,即尝试更改在同一表达式的其他地方也使用的变量。 (例如:a == --a。)如果这样做,则表达式未定义。例外情况是当访问的值被用于计算要存储的值时,如i = i + 1

【讨论】:

    【解决方案2】:

    逻辑&amp;&amp; 运算符在第一个和第二个操作数的计算之间包含一个序列点。部分原因是左侧的任何副作用(例如 -- 运算符执行的副作用)在评估右侧之前完成。

    这在C standard 的第 6.5.13p4 节中详细介绍了逻辑 AND 运算符:

    与按位二进制 & 运算符不同,&& 运算符保证 从左到右的评估;如果计算第二个操作数, 的评估之间有一个序列点 第一个和第二个操作数。如果第一个操作数比较 等于 0,不计算第二个操作数。

    在这个表达式的情况下:

    (a-- == 10 && a-- == 9)
    

    首先将a (10) 的当前值与 10 进行比较是否相等。这是真的,因此然后评估右侧,但不是在左侧完成递减 a 的副作用之前边。然后,将a(现在为 9)的当前值与 9 进行比较是否相等。这也是正确的,因此整个表达式的计算结果为 true。在执行下一条语句之前,在右侧完成的递减a 的副作用已经完成。

    但是这个表达式:

    if (a == --a)
    

    涉及在没有序列点的同一表达式中读取和写入a。这会调用undefined behavior

    【讨论】:

      【解决方案3】:

      使用“逻辑与”运算符(a-- == 10 &amp;&amp; a-- == 9) 是格式良好的表达式(没有a++ + a++ 中的未定义行为)。

      C 标准提到“逻辑与”/“逻辑或”运算符:

      保证从左到右的评价;之后有一个序列点 第一个操作数的求值。

      因此,第一个子表达式 a-- == 10 的所有副作用在第二个子表达式 a-- == 9 评估之前完成。 a 在评估第二个子表达式之前是 9

      【讨论】:

      • 目前,这仅解决了两个if 语句中的第一个(但考虑到已编辑的问题,它这样做是正确的)。
      • 同意,在第二个if 中,== 操作数求值的顺序未定义,因此运算符结果也未定义。
      猜你喜欢
      • 2014-07-07
      • 2016-06-28
      • 2014-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-26
      相关资源
      最近更新 更多