【发布时间】:2018-05-21 23:35:30
【问题描述】:
在你开始大喊未定义的行为之前,这是在N4659 (C++17)
中明确列出的 i = i++ + 1; // the value of i is incremented
i = i++ + 1; // the behavior is undefined
发生了什么变化?
据我所知,来自[N4659 basic.exec]
除非另有说明,对单个运算符的操作数和单个表达式的子表达式的求值是无序的。 [...]运算符的操作数的值计算在运算符结果的值计算之前排序。如果内存位置上的副作用相对于同一内存位置上的另一个副作用或使用同一内存位置中任何对象的值的值计算是无序的,并且它们不是潜在的并发,则行为未定义。
其中值定义在[N4659 basic.type]
对于一般可复制的类型,值表示是对象表示中的一组位,用于确定一个值,它是实现定义的一组值的一个离散元素
除非另有说明,对单个运算符的操作数和单个表达式的子表达式的求值是无序的。 [...]运算符的操作数的值计算在运算符结果的值计算之前排序。如果标量对象的副作用相对于同一标量对象的另一个副作用或使用同一标量对象的值的值计算是未排序的,则行为未定义。
同样,值定义在[N3337 basic.type]
对于普通可复制类型,值表示是对象表示中的一组位,用于确定一个值,它是实现定义的一组值的一个离散元素。
除了提到无关紧要的并发性之外,它们是相同的,并且使用内存位置而不是标量对象,其中
算术类型、枚举类型、指针类型、指向成员类型的指针、
std::nullptr_t以及这些类型的 cv 限定版本统称为标量类型。
这不会影响示例。
赋值运算符 (=) 和复合赋值运算符都从右到左分组。所有这些都需要一个可修改的左值作为它们的左操作数,并返回一个指向左操作数的左值。如果左操作数是位域,则所有情况下的结果都是位域。在所有情况下,赋值都在左右操作数的值计算之后和赋值表达式的值计算之前进行排序。右操作数在左操作数之前排序。
赋值运算符 (=) 和复合赋值运算符都从右到左分组。所有这些都需要一个可修改的左值作为它们的左操作数,并返回一个指向左操作数的左值。如果左操作数是位域,则所有情况下的结果都是位域。在所有情况下,赋值都是在左右操作数的值计算之后,赋值表达式的值计算之前进行的。
唯一的区别是 N3337 中没有最后一句。
然而,最后一句话不应该有任何重要性,因为左操作数i既不是“另一个副作用”也不是“使用相同标量对象的值” 因为 id-expression 是一个左值。
【问题讨论】:
-
您已经确定了原因:在 C++17 中,右操作数在左操作数之前排序。在 C++11 中没有这样的顺序。你的问题究竟是什么?
-
@Robᵩ 见最后一句话。
-
有没有人知道这个改变的动机?我希望静态分析器在面对
i = i++ + 1;之类的代码时能够说“你不想那样做”。 -
@NeilButterworth,来自论文p0145r3.pdf:“为惯用 C++ 优化表达式评估顺序”。
-
@NeilButterworth,第 2 节说这是违反直觉的,即使专家也无法在所有情况下做正确的事情。这几乎就是他们的全部动力。
标签: c++ language-lawyer c++17