c++ - 未排序的值计算(a.k.a 序列点)

标签 c++ language-lawyer side-effects sequence-points

很抱歉再次打开这个话题,但是考虑这个话题本身已经开始给我一个未定义的行为。想要进入定义明确的行为区域。

给定

int i = 0;
int v[10];
i = ++i;     //Expr1
i = i++;     //Expr2
++ ++i;      //Expr3
i = v[i++];  //Expr4

我认为上述表达式(按此顺序)为

operator=(i, operator++(i))    ; //Expr1 equivalent
operator=(i, operator++(i, 0)) ; //Expr2 equivalent
operator++(operator++(i))      ; //Expr3 equivalent
operator=(i, operator[](operator++(i, 0)); //Expr4 equivalent

现在说到这里的行为是来自 C++ 0x 的重要引述。

$1.9/12- "Evaluation of an expression (or a sub-expression) in general includes both value computations (including determining the identity of an object for lvalue evaluation and fetchinga value previously assigned to an object for rvalue evaluation) and initiation of side effects."

$1.9/15- "If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined."

[ Note: Value computations and side effects associated with different argument expressions are unsequenced. —end note ]

$3.9/9- "Arithmetic types (3.9.1), enumeration types, pointer types, pointer to member types (3.9.2), std::nullptr_t, and cv-qualified versions of these types (3.9.3) are collectively called scalar types."

  • 在 Expr1 中,表达式 i(第一个参数)的计算相对于表达式 operator++(i)(其中有副作用)。

    因此 Expr1 具有未定义的行为。

  • 在 Expr2 中,表达式 i(第一个参数)的计算相对于表达式 operator++(i, 0) 的计算是无序的(有副作用)'。

    因此 Expr2 具有未定义的行为。

  • 在 Expr3 中,单独参数 operator++(i) 的计算需要在外部 operator++ 被调用之前完成。

    因此 Expr3 具有明确定义的行为。

  • 在 Expr4 中,表达式 i(第一个参数)的计算相对于 operator[](operator++(i, 0)(有副作用)。

    因此 Expr4 具有未定义的行为。

这种理解正确吗?


附: OP中分析表达式的方法是不正确的。这是因为,正如@Potatoswatter 所指出的那样 - “第 13.6 条不适用。请参阅 13.6/1 中的免责声明,”这些候选函数参与 13.3.1.2 中描述的运算符重载解决过程,并且不用于其他目的。 "它们只是虚拟声明;不存在与内置运算符相关的函数调用语义。"

最佳答案

native 运算符表达式不等同于重载运算符表达式。值与函数参数的绑定(bind)有一个序列点,这使得 operator++() 版本定义良好。但这对于原生类型的情况是不存在的。

在所有四种情况下,i 在完整表达式中更改了两次。由于表达式中没有出现 ||&&,这就是即时 UB。

§5/4:

Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression.

为 C++0x 编辑(更新)

§1.9/15:

The value computations of the operands of an operator are sequenced before the value computation of the result of the operator. If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.

但是请注意,值计算和副作用是两个不同的东西。如果 ++i 等价于 i = i+1,则 + 是计算值,而 = 是副作用。从 1.9/12 开始:

Evaluation of an expression (or a sub-expression) in general includes both value computations (including determining the identity of an object for glvalue evaluation and fetching a value previously assigned to an object for prvalue evaluation) and initiation of side effects.

因此,尽管 C++0x 中的值计算比 C++03 中的排序更强,但 副作用不是。 同一表达式中的两个副作用,除非另外排序,否则会产生 UB .

无论如何,值计算都是按它们的数据依赖关系排序的,并且没有副作用,它们的评估顺序是不可观察的,所以我不确定为什么 C++0x 会麻烦说什么,但这只是意味着我需要阅读更多 Boehm 和 friend 们写的论文。

编辑#3:

感谢 Johannes 解决了我在 PDF 阅读器搜索栏中输入“sequenced”的懒惰问题。无论如何,我要去 sleep 并起床进行最后两个编辑......对;v)。

§5.17/1 定义赋值运算符说

In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.

还有关于预增量运算符的 §5.3.2/1 说

If x is not of type bool, the expression ++x is equivalent to x+=1 [Note: see … addition (5.7) and assignment operators (5.17) …].

通过这个标识,++++ x(x +=1) +=1 的简写。所以,让我们解释一下。

  • 评估远端 RHS 上的 1 并下降到括号中。
  • 计算内部1x的值(prvalue)和地址(glvalue)。
  • 现在我们需要 += 子表达式的值。
    • 我们已经完成了该子表达式的值计算。
    • 赋值副作用必须在赋值的值可用之前排序!
  • 将新值赋给x,这与子表达式的glvalue和prvalue结果相同。
  • 我们现在已经走出困境了。整个表达式现在已简化为 x +=1

那么, 1 和 3 是明确定义的,而 2 和 4 是未定义的行为,这是您所期望的。

我在 N3126 中搜索“sequenced”时发现的唯一另一个惊喜是 5.3.4/16,其中允许实现在评估构造函数参数之前调用 operator new。太酷了。

编辑#4:(哦,我们编织了多么纠结的网)

Johannes 再次指出,在 i ==++i; 中,i 的泛左值(也称为地址)模糊地依赖于 ++i。 glvalue 肯定是 ia 值,但我不认为 1.9/15 打算包含它,原因很简单,命名对象的 glvalue 是常量,并且实际上不能有依赖关系。

对于一个知识渊博的稻草人,考虑

( i % 2? i : j ) = ++ i; // certainly undefined

这里,= 的 LHS 的左值取决于对 i 的右值的副作用。 i 的地址没有问题; ?: 的结果是。

也许一个很好的反例是

int i = 3, &j = i;
j = ++ i;

这里 j 有一个与 i 不同(但相同)的 glvalue。这是明确定义的,但 i =++i 不是吗?这表示编译器可以应用于任何情况的微不足道的转换。

1.9/15 应该说

If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the prvalue of the same scalar object, the behavior is undefined.

关于c++ - 未排序的值计算(a.k.a 序列点),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3852768/

相关文章:

c++ - 将虚拟锁传递给 std::condition_variable_any::wait

java - toString 副作用是否应用于 java 调试?

c++ - 如果 f 修改 x,x*f(x) 的值是否未指定?

c++ - 如何从 boost 库中的文件中读取数据

java - 匿名类可以完全不可变吗?

c++ - 用整型变量重载类名

c++ - 后递减运算符和逻辑运算符之间的交互

c++ - 在 C++ 中将对象传递给函数时的副作用

c++ - 遍历 constexpr 数组

c++ - IncrCalcPtrs 期间的内部错误 - 当我第二次运行代码时不会再次发生