++i + ++i 是否是未定义的行为?

3
考虑以下代码:
int main(){
  int i = 0;
  int a = ++i + ++i;
}

我找不到任何说明+操作数无顺序的信息。因此,根据标准,二进制+操作数的顺序是不确定的。

[介绍,执行]/15

对于任意两个评估 A 和 B,如果 A 在 B 之前排序(或者等价地,B 在 A 之后排序),则 A 的执行必须在 B 之前。如果 A 没有排序在 B 之前,并且 B 没有排序在 A 之前,则 A 和 B 是无序的。[注:未排序评估的执行可以重叠。—end note]

当 A 在 B 之前排序或 B 在 A 之前排序时,评估 A 和 B 是不确定排序的。[注:不确定排序的评估不能重叠,但可以先执行其中一个。—end note]

该引用的意思是评估A可以在B之前发生,或者评估B可以在A之前发生。而无序评估的执行可以重叠,而不确定顺序的评估不能重叠,它们是不同的。
我们知道由于前缀++,i的修改总是在i的值计算之前发生。
然后根据规则:
表达式(或子表达式)的评估通常包括值计算(包括确定用于glvalue评估的对象的标识和获取先前分配给对象的值以进行prvalue评估)和副作用的启动
如果对内存位置的副作用与同一内存位置上的另一个副作用或使用同一内存位置中任何对象的值进行的值计算不确定顺序,并且它们不可能并发,则行为未定义。
无论是在 A 评估之前还是之后对 B 进行评估,与相应值计算或 ++i + ++i; 的副作用无关的副作用不存在。因为不确定顺序的评估不能重叠,因此两个评估中的一个必须在另一个之前完全执行。评估包括值计算和副作用。因此,在另一个之前计算了一个增量到 i

然而,未排序的评估遵循不同的规则,因此如果二元 + 操作数的评估是未排序的而不是不确定顺序的,则混淆将得到解决。如果我在上面的分析中错过了标准中的某些内容,请纠正我。

更新

我发现以下句子,它似乎表明评估是无序的:

除非另有说明,否则单个运算符的操作数和单个表达式的子表达式的评估没有顺序

然而,我不知道如何正确理解这个句子。我想到了两种解释:

对于运算符A,其操作数的评估彼此之间是不按顺序进行的;对于表达式B,其子表达式的评估彼此之间也是不按顺序进行的。
并且
将个别运算符的操作数的评估视为A。将个别表达式的子表达式的评估视为B。A与B彼此之间是不按顺序进行的。
哪种解释是正确的?

3
https://zh.wikipedia.org/wiki/KISS%E5%8E%9F%E5%88%99 - 简单即是美 - Ed Heal
1
@EdHeal 是的,我只是想了解这些基于标准的规则,我认为没有人能够编写出这样的代码。 - xmh0511
2
应该使用“should” 而不是“could”。至于标准,它在这个问题上相当模糊。只需编写易于理解的可读代码即可。 - Ed Heal
对@EdHeal评论点赞。 (Ed请修复维基链接。)除非您是学者或喜欢琐事,否则如果在规范中找不到某个表达式或语句的明确解释,请避免使用它。 - rtx13
1
关于您的更新,正确的阅读方式是“取所有操作数的集合,联合所有子表达式的集合形成一个组合集合。该集合中的每个元素在与其他元素无序(除非另一条规则介入并保证这两个元素的排序)。”由于操作数子表达式,您尝试绘制两个替代方案需要一个根本不存在的二分法。 - Ben Voigt
显示剩余14条评论
2个回答

3
标准文本似乎意味着行为是未定义的。
- 在 `+`中,`` 和 `` 的评估是无序的。 - 两个部分具有影响相同内存位置的副作用。
(1)我认为这一部分明确清晰,但我不确定是否有其他部分正在表明相反的情况,或者某些更高级别的概念(例如程序执行是什么)因矛盾规则而出现逻辑错误。 鉴于C++的复杂性,如果没有错误存在,我会感到非常惊讶。
(2)对于重载的 `operator+`,它们将被视为不确定顺序(因为规则与函数调用相同,因此不是未定义的行为:N4713的8.5.1.2[5]说“后缀表达式在表达式列表中的每个表达式和默认参数之前先排序。初始化参数,包括每个相关的值计算和副作用,其顺序与任何其他参数是不确定的。”),但对于原生的 `int` 来说,这并不适用,行为是未定义的。
(3)文本说,“除非另有说明,在单独的表达式的各个运算对象以及各个子表达式的评估都是无序的”。当然,对于一元运算符,这个问题是不相关的(没有排序可谈),对于三元的 `?:`运算符,有特殊的排序规则。 “子表达式”的部分是为了覆盖像 `a[++i][++i]` 这样的情况,其中 `a` 是例如 `char **`:在这种情况下,两个相同的子表达式 `++i`是无序的,并且具有修改相同内存位置的副作用,因此是未定义行为。 我认为这段话实际上比必要复杂,因为运算符的操作数也是表达式的子表达式,因此最后一部分就足够了。

2
@jackX:没错... "除非另有说明,否则单个运算符的操作数和单个表达式的子表达式的评估是无序的。" - 6502
@jackX:情况如下:1)A在B之前排序;2)B在A之前排序;3)A在相对于B不确定的顺序中排序;4)未排序。如果情况4中的两个部分具有副作用,会更改相同的内存位置,则行为是未定义的。情况4也是默认情况,除非存在特定规则。 - 6502
@jackX:当前标准明确规定,在计算a+b时,实际的求和是在计算ab的值之后进行的。在我看来,有些不合逻辑的是,++i的值取决于副作用(++i的值是一个引用,换句话说是一个地址,并且由于增量而不改变),但这并不重要。问题在于同一内存位置的两个未排序的增量。 - 6502
1
@jackX:术语“不确定顺序”有非常严格的含义,与“未排序”非常不同:它意味着它们已排序,但未指定如何排序(在多次评估相同表达式的情况下,顺序甚至可能不相同)。然而,在何时适用“不确定顺序”保证是指定的(例如函数调用中的参数...请参见N4713中的8.5.1.2 [5]); 当没有说明时,默认值为“未排序”,并且这一点已经明确说明。 - 6502
2
@jackX: "未排序"并不意味着顺序是随机的,它意味着每个评估的部分甚至可以交错在一起。 "不确定排序"意味着评估之间没有重叠,但没有指定哪个先发生。 - Ben Voigt
显示剩余17条评论

2
让我来回答一下,以使问题的答案更加清晰。首先考虑以下句子:

除非另有说明,否则单个运算符的操作数和单个表达式的子表达式的评估是无序的。

这句话可以分成两部分:

1.除非另有说明,单个运算符的操作数的评估是无序的。
2.除非另有说明,单个表达式的子表达式的评估是无序的。

第一部分是什么意思?它表示一个包含运算符的操作数的集合X,集合X的每个元素与彼此都是无序的,它们是无序的。第二部分类似于第一部分,只是让集合X包含表达式的子表达式。
在[expr.additive]中描述了二元运算符+的语句,并且该部分没有关于操作数顺序的规定,因此规则1适用于该运算符。因此,由于标准规定无序可以重叠,这是什么意思?它意味着“每个评估的部分甚至可以交错在一起”(@Ben Voigt在评论中说)。因此,对于“++i + ++i”,通常情况是:

如果对内存位置的副作用在相对于同一内存位置上的另一个副作用或使用同一内存位置中任何对象的值进行的值计算是无序的,并且它们不是可能并发的,则行为是未定义的。

因此,“int a = ++i + ++i”是未定义的行为,这个问题的关键是理解下面这句话:

除非另有说明,否则单个运算符的操作数和单个表达式的子表达式的评估是无序的。


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