是什么让我=我++ + 1; 在C ++ 17合法吗?

在你开始大吼未定义的行为之前,这在N4659(C ++ 17)中明确列出,

  i = i++ + 1;        // the value of i is incremented

然而在N3337(C ++ 11)

  i = i++ + 1;        // the behavior is undefined

什么改变了?

从我可以收集的信息中,[N4659 basic.exec]

除非另有说明,否则对个别操作符和个别表达式的操作数的评估是不确定的。 [...]运算符操作数的值计算在运算符结果的值计算之前排序。 如果对存储器位置的副作用相对于同一存储器位置上的另一副作用或使用同一存储器位置中的任何对象的值进行值计算而未被确定,并且它们不可能是并发的,则行为是未定义的。

价值定义在[N4659 basic.type]

对于普通可复制类型,值表示是对象表示中的一组位,用于确定一个值,该值是实现定义的一组值的一个离散元素

从[N3337 basic.exec]

除非另有说明,否则对个别操作符和个别表达式的操作数的评估是不确定的。 [...]运算符操作数的值计算在运算符结果的值计算之前排序。 如果对标量对象的副作用相对于同一标量对象的另一副作用或使用同一标量对象的值进行值计算而言是不确定的,则行为是未定义的。

同样,值定义在[N3337 basic.type]

对于普通可复制类型,值表示是对象表示中的一组位,用于确定一个值,该值是实现定义的一组值中的一个离散元素。

它们是相同的,只是提到并发性并不重要,并且使用内存位置而不是标量对象,其中

算术类型,枚举类型,指针类型,成员类型指针, std::nullptr_t和这些类型的cv限定版本统称为标量类型。

这不影响这个例子。

从[N4659 expr.ass]

赋值运算符(=)和复合赋值运算符全部从右到左组合。 所有需要一个可修改的左值作为它们的左操作数并返回一个左值指向左操作数。 如果左操作数是位域,则所有情况下的结果都是位域。 在所有情况下,赋值都在左右操作数的值计算之后,赋值表达式的值计算之前进行排序。 右操作数在左操作数之前排序。

从[N3337 expr.ass]

赋值运算符(=)和复合赋值运算符全部从右到左组合。 所有需要一个可修改的左值作为它们的左操作数并返回一个左值指向左操作数。 如果左操作数是位域,则所有情况下的结果都是位域。 在所有情况下,赋值都在左右操作数的值计算之后,赋值表达式的值计算之前进行排序。

唯一的区别是N3337中没有最后一句话。

然而,最后一句话应该没有什么重要性,因为左操作数i既不是“另一个副作用”也不是“使用相同标量对象的值”,因为id表达式是一个左值。


在C ++ 11中,“赋值”的行为(即修改LHS的副作用)在右操作数的值计算之后被排序。 请注意,这是一个相对“弱”的保证:它仅在与RHS的值计算相关时产生排序。 它没有提到可能存在于RHS中的副作用,因为副作用的发生不是价值计算的一部分。 C ++ 11的要求没有在分配行为和RHS的任何副作用之间建立相对顺序。 这就是UB的潜力。

在这种情况下唯一的希望是RHS使用的特定运营商提供的任何额外保证。 如果RHS使用的前缀++ ,具体到的前缀形式测序性质++会在这个例子中化险为夷。 但是,后缀++是一个不同的故事:它并没有做出这样的保证。 在这个例子中,在C ++ 11中, =和postfix ++的副作用最终不会相互关联。 这就是UB。

在C ++ 17中,额外的句子被添加到赋值运算符的规范中:

右操作数在左操作数之前排序。

与上述结合起来,它提供了非常有力的保证。 它在LHS发生的所有事情之前对发生在RHS中的所有事情(包括任何副作用)进行排序。 由于实际任务是在LHS(和RHS)之后进行测序的,所以额外的测序完全隔离了RHS中存在的任何副作用。 这种更强大的排序是消除了上述UB。

(已更新,以考虑约翰伯林杰的评论。)


你确定了新的句子

右操作数在左操作数之前排序。

并且您正确地确定左操作数作为左值的评估是无关紧要的。 然而,之前的顺序被指定为传递关系。 完整的右操作数(包括后增量)因此也在赋值之前排序。 在C ++ 11中,只有右操作数的值计算在赋值之前被排序。


在较早的C ++标准和C11中,赋值运算符文本的定义以文本结尾:

操作数的评估是不确定的。

这意味着操作数中的副作用是不确定的,因此如果它们使用相同的变量,则肯定是未定义的行为。

这个文本在C ++ 11中被简单地删除了,这使得它有点模棱两可。 它是UB还是不是? 这已经在C ++ 17中得到澄清,他们补充道:

右操作数在左操作数之前排序。


作为一个侧面说明,在更老的标准中,这一切都非常明确,例如C99:

操作数的评估顺序未指定。 如果尝试修改赋值运算符的结果或在下一个顺序点之后访问它,则行为是未定义的。

基本上,在C11 / C ++ 11中,当他们删除这些文本时,他们搞砸了。

链接地址: http://www.djcxy.com/p/73221.html

上一篇: What made i = i++ + 1; legal in C++17?

下一篇: Is modifying the pointed value and the pointer at the same time UB