序列点与运算符优先级

Sequence Points vs Operator Precedence

本文关键字:运算符 优先级      更新时间:2023-10-16

可能重复:
无序列值计算(也称为序列点)
未定义的行为和序列点
操作员优先级与评估顺序

我仍然试图理解以下表达式是如何导致未定义行为的:

a = a++;

在搜索SO时,我发现了以下问题:

序列点和运算符优先级之间的区别?0_o

我通读了所有的答案,但在细节上仍然有困难。其中一个答案将我上面的代码示例的行为描述为不明确,即a是如何修改的。例如,它可以归结为以下两种:

a=(a+1);a++;
a++;a=a;

究竟是什么使得a的修改含糊不清?这与不同平台上的CPU指令有关吗?优化器如何利用未定义的行为?换句话说,它似乎是未定义的,因为生成的汇编程序?

我看不出编译器使用a=(a+1);a++;的原因,它只是看起来很古怪,没有多大意义。编译器拥有什么才能使它以这种方式运行?

编辑:

需要明确的是,我确实理解发生了什么,我只是不明白当有关于运算符优先级的规则(本质上定义了表达式的求值顺序)时,它是如何被定义的。在这种情况下,赋值发生在最后,因此需要首先评估a++,以确定要赋值给a的值。因此,我所期望的是,在修复后增量期间,首先修改a,然后生成一个值,将其分配回a(第二次修改)。但运算符优先级的规则似乎让我非常清楚这种行为,我找不到任何"回旋余地"让它有未定义的行为。

您链接到的问题中的第一个答案准确地解释了发生了什么。我会尝试重新措辞,使其更清楚。

运算符优先级定义了通过表达式计算值的顺序。表达式CCD_ 8的结果是很好理解的。

但是 ,变量a的修改不是表达式的一部分。是的,真的。这是你难以理解的部分,但这只是C和C++对它的定义

表达式会产生值,但某些表达式可能会产生副作用。表达式a = 1的值为1,但它也具有将变量a设置为1副作用。就C和C++如何定义事物而言,这是两个不同的步骤。类似地,a++具有值和副作用。

序列点定义副作用对在这些序列点之后求值的表达式可见的时间。运算符优先级与序列点无关。这就是C/C++定义事物的方式。

这可能是一个过于简单的解释,但我认为这是因为当代码用"a"完成"时,没有办法解决。它是在增量之后完成的,还是在赋值之后完成的?决议最终是循环的。增量后的赋值会更改应用增量值时的语义。也就是说,在"a"增加之前,代码不会使用"a"完成,但在进行赋值之后,a才会增加。这几乎是死锁的语言版本。

正如我所说,我相信这不是一个很好的"学术"解释,但这就是我把它藏在自己耳朵里的方式。希望这能有所帮助。

优先级规则指定表达式的求值顺序,但在求值过程中不必发生副作用。它们可以在下一个序列点之前的任何时间发生。

在这种情况下,增量的副作用既不在赋值之前也不在赋值之后排序,因此表达式具有未定义的行为。

这里的重点是,在某些CPU体系结构(如英特尔安腾)上,编译器可以在指令级并行化这两个操作,但如果您的构造定义明确,则禁止这样做。在制定序列点规范时,这些体系结构大多是假设性的,由于安腾是一个失败的,因此到2012年,这在很大程度上是语言中不必要的复杂性。在任何仍在使用的体系结构上,基本上都没有可能的缺点——即使对于安腾来说,性能优势也是微乎其微的,编写一个甚至可以利用它的编译器也非常头疼。

还需要注意的是,在C++11中,序列点被替换为前序和后序,这使得更多类似的情况得到了明确定义。

语句a=a++有两个结果和两个赋值:

a=a

(因为这是后增长)和

a=a+1

这些赋值显然会导致a的最终值不同。

c标准的起草者没有指出两个赋值中的哪一个应该首先写入a,哪一个第二个,因此编译器编写者可以在任何给定的情况下自由选择他们喜欢的。

结果是,它(这个特定的语句)不会崩溃,但您的程序不能再依赖于具有特定值的。

让我浏览一下语句a = a++中的基本问题。我们希望实现以下所有目标:

  • 确定值aa++的返回值,#1)

  • 增加aa++的副作用,#2)

  • 将旧值分配给a(分配的效果,#3)

有两种可能的排序方式:

  1. 将原始a存储到a中(无操作);则递增CCD_ 26。与a = a; ++a;相同。这是序列#1-#3-#2。

  2. 评估a,递增a,将原始值分配回a。与b = a; ++a; a = b;相同。这是序列#1-#2-#3。

由于没有规定的顺序,因此这两种操作都是允许的。但他们最终会有不同的结果。两个序列都不比另一个更自然。