当我阅读(答案)那么为什么在C++11中i = ++i + 1是良定义的?时,出现了这个问题。
我了解到微妙的解释是:(1)表达式++i
返回一个lvalue,但+
需要prvalues作为操作数,因此必须执行从lvalue到prvalue的转换;这涉及获取该lvalue的当前值(而不是比i
旧值多一),因此必须在增量的副作用之后进行排序(即更新i
)(2)赋值的LHS也是lvalue,因此其值评估不涉及获取i
的当前值;虽然这个值计算与RHS的值计算未排序,但这并不会导致问题(3)赋值本身的值计算涉及再次更新i
,但在其RHS的值计算之后排序,因此在先前更新i
之后没有问题。
=
更改为+=
(或类似的运算符),会怎样呢?
据我所见,这个标准似乎在这里自相矛盾。由于评估表达式
i += ++i + 1
会导致未定义行为吗?
+=
的左侧仍然是一个lvalue(其右侧仍然是一个prvalue),因此与上面相同的推理适用于(1)和(2); 在 +=
的操作中,没有未定义的行为。至于(3),复合赋值 +=
的操作(更精确地说是该操作的副作用;如果需要,其值计算在任何情况下都在其副作用之后被序列化)现在必须同时获取 i
的当前值,并将RHS添加到其中,然后将结果存回 i
。(显然,在其之后进行排序,即使标准未明确说明此类运算符的评估也会始终引发未定义的行为)。如果它们与 ++
的副作用未排序,则这两个操作都将产生未定义的行为,但正如上述论证一样( ++
的副作用在 +
的值计算之前被排序,给出了 +=
运算符的RHS,该值计算在该复合赋值的操作之前被排序),这不是问题。
另一方面,标准也说E += F
等同于E = E + F
,除了(左值)E只被计算一次。现在在我们的例子中,变量i
的值计算作为左值时不涉及需要按照其他操作顺序进行排序的任何内容,因此计算一次或两次没有区别;我们的表达式应严格等同于E = E + F
。但是这里有一个问题:很明显,计算i = i + (++i + 1)
将导致未定义的行为!怎么回事?或者这是标准的缺陷吗?
已添加。 我稍微修改了上面的讨论,更好地区分了副作用和值计算,并使用“评估”(与标准一样)来包含两者。我认为我的主要问题不仅在于这个例子中行为是否被定义,而且如何阅读标准以决定这个问题。特别是,对于复合赋值操作,应该将E op = F
的等价性视为语义的终极权威(这种情况下,该示例明显具有UB),还是仅仅作为确定要赋值的值所涉及的数学操作的指示(即由op
所识别的操作,其中复合赋值运算符的LHS转换为RHS作为左操作数,其RHS作为右操作数)。后者选项使得在这个例子中争论UB变得更加困难,我已经试图解释过。我承认,将等价性强制执行(使得复合赋值变成第二类基元,其含义通过重写以一类原语的方式给出;因此,语言定义会变得简化)是很诱人的,但是有相当强的反对意见:
E
只计算一次”的例外。请注意,这个例外很重要,以避免在计算E
时涉及副作用未定义行为的情况下进行任何使用,例如在相当常见的a[i++] += b;
用法中。事实上,我认为没有绝对等价的重写来消除复合赋值;使用虚构的|||
运算符来指定无序计算,可以尝试将E op= F;
(以int
操作数为简单起见)定义为等效于{ int& L=E ||| int R=F; L = L + R; }
,但是这样,该示例就不再具有UB。在任何情况下,标准都没有给我们提供重写配方。标准不将复合赋值视为二等公民,不需要单独定义语义。例如,在5.17中(我强调的部分):
赋值运算符(=)和复合赋值运算符都从右到左组合。[...] 在所有情况下,赋值在右操作数和左操作数的值计算之后,并在赋值表达式的值计算之前进行排序。关于一个不确定顺序的函数调用,复合赋值的操作是单个评估。
如果意图是让复合赋值成为简单赋值的简写,那么没有理由在此描述中明确包含它们。最后一句话甚至直接与如果等价性被视为权威所应该发生的情况相矛盾。
如果承认复合赋值有自己的语义,那么问题就在于它们的评估涉及(除了数学运算之外)不仅是副作用(赋值)和值评估(在赋值之后顺序进行),还包括一个未命名操作来获取LHS的(先前的)值。这通常会在“lvalue-to-rvalue转换”的标题下处理,但在这里这样做很难证明,因为没有运算符将LHS作为rvalue操作数(尽管在扩展的“等效”形式中有一个)。正是这个未命名操作与++的副作用存在潜在的未排序关系,但是这种未排序关系在标准中没有明确说明,因为未命名操作不存在。使用其存在只是在标准中隐含存在的操作来证明UB是很难证明的。