无序值计算(a.k.a序列点) [英] Unsequenced value computations (a.k.a sequence points)

查看:161
本文介绍了无序值计算(a.k.a序列点)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对不起,再次打开这个主题,但是思考这个主题本身已经开始给我一个未定义的行为。





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

我认为上面的表达式
$ b

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

$ b b

现在介绍的行为是来自 C ++ 0x 的重要引号。


$ 1.9 / 12-表达式的评估
(或子表达式)一般来说
包括两个值计算
(包括确定
的身份用于左值评估的对象,
fetchinga值以前分配给
用于右值计算的对象)和
启动副作用。



$ 1.9 / 15-如果对标量
对象的副作用不是相对于
的顺序,对相同
标量对象的另一个副作用
计算使用
相同标量对象的值,行为是
未定义。



[注意:值计算和边
不同的
参数表达式无效。
-end note]



$ 3.9 / 9-算术类型(3.9.1),
枚举类型,指针类型
指向成员类型的指针(3.9.2),
std :: nullptr_t和cv限定的
这些类型的版本(3.9.3)是
,统称为标量类型。





  • 在Expr1中,表达式 i (第一个参数),对于exp $ 运算符++(i)(具有副作用)的求值是无效的。



    因此Expr1有未定义的行为。


  • 在Expr2中, c> i (第一个参数),对于expession 运算符++(i,0) )'。



    因此Expr2有未定义的行为。


  • Expr3,在调用外部运算符++ 之前,需要完成对单独参数运算符++(i)的求值。


  • 在Expr4中,对于运算符[](运算符++(i,0))的评估,表达式 i code>(有副作用)。



    因此Expr4有未定义的行为。 b $ b



这是否正确?






在OP中分析表达式的方法是不正确的。这是因为,作为@Potatoswatter,注释 - 条款13.6不适用。参见13.6 / 1中的免责声明,这些候选函数参与13.3.1.2中描述的运算符重载解决过程,并且不用于其他目的。

解决方案

本地运算符表达式不等同于重载的运算符表达式。在将值绑定到函数参数时有一个序列点,这使得运算符++()版本定义良好。



在所有四种情况下, i 全表达。由于 || &&


p>在上一个和下一个序列点之间,标量对象的存储值最多只能通过表达式的求值修改一次。




Edit for C ++ 0x(updated)



§1.9/ 15:


在运算符结果的值计算之前,对运算符的操作数的值计算进行排序。如果对标量对象的副作用相对于对同一标量对象的另一个副作用或使用相同标量对象的值的计算没有序列,则该行为是未定义的。




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


表达式(或子表达式)的评估通常包括两个值计算(包括确定一个对象用于glvalue评估和获取以前分配给一个对象的值以进行prvalue评估)和启动副作用。


值计算在C ++ 0x中比C ++ 03更强地排序,副作用不是。相同表达中的两个副作用,除非另有排序,产生UB。



值计算按照它们的数据依赖性进行排序,副作用不存在,它们的求值顺序是不可见的,所以我不知道为什么C ++ 0x会遇到麻烦。



编辑#3:



感谢Johannes应对我的懒惰,在我的PDF阅读器搜索栏中输入sequenced。



§5.17/ 1定义赋值运算符


在所有情况下,赋值在右和左操作数的值计算之后,在赋值表达式的值计算之前排序。


还有§5.3.2/ 1上的预增量运算符


如果x不是bool类型,表达式++ x等价于x + = 1 [注:见...添加(5.7)和赋值运算符(5.17)...]。




通过此身份, ++ ++ x (x + = 1)+ = 1




  • 评估远RHS和下降时的 1 c> 1 和值(prvalue)和地址(glvalue) x

  • 现在我们需要+ =子表达式的值。

    • 我们完成了该子表达式的计算。

    • 赋值副作用必须在赋值之前可用!


  • 将新值分配给 x

  • 我们现在已经走出树林了。整个表达式现在已减少到 x + = 1



em> 所以,然后 1和3是定义明确的,2和4是未定义的行为,你会期望。



我通过在N3126中搜索顺序找到的唯一的另一个惊喜是5.3.4 / 16,其中在评估构造函数参数之前允许实现调用 operator new 。这很酷。



编辑#4 :(哦,什么是我们编织的缠结网)



Johannes在 i == ++ i; 中, i 的glvalue(aka地址) c $ c> ++ i 。 glvalue肯定是 i 的 值,但我不认为1.9 / 15意在包括它的简单的原因是glvalue



对于一个信息丰富的strawman,请考虑

 (i%2?i:j)= ++ i; //当然未定义

这里, = 取决于对 i 的价格的副作用。 i 的地址不是问题; ?:的结果是



也许一个好的反例是

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

这里 j 但等同于) i 。这是很好定义,但 i = ++ i 不是?这代表了编译器可以应用于任何情况的微小变换。



1.9 / 15应该说


如果对标量对象的副作用相对于对同一标量对象的另一个副作用或使用同一标量对象的 prvalue 的值计算没有排序,则行为是未定义。



Sorry for opening this topic again, but thinking about this topic itself has started giving me an Undefined Behavior. Want to move into the zone of well-defined behavior.

Given

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

I think of the above expressions (in that order) as

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

Now coming to behaviors here are the important quotes from 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."

  • In Expr1, the evaluation of the expression i (first argument), is unsequenced with respect to the evaluation of the expession operator++(i) (which has a side effect).

    Hence Expr1 has undefined behavior.

  • In Expr2, the evaluation of the expression i (first argument), is unsequenced with respect to the evaluation of the expession operator++(i, 0) (which has a side effect)'.

    Hence Expr2 has undefined behavior.

  • In Expr3, the evaluation of the lone argument operator++(i) is required to be complete before the outer operator++ is called.

    Hence Expr3 has well defined behavior.

  • In Expr4, the evaluation of the expression i (first argument) is unsequenced with respect to the evaluation of the operator[](operator++(i, 0) (which has a side effect).

    Hence Expr4 has undefined behavior.

Is this understanding correct?


P.S. The method of analyzing the expressions as in OP is not correct. This is because, as @Potatoswatter, notes - "clause 13.6 does not apply. See the disclaimer in 13.6/1, "These candidate functions participate in the operator overload resolution process as described in 13.3.1.2 and are used for no other purpose." They are just dummy declarations; no function-call semantics exist with respect to built-in operators."

解决方案

Native operator expressions are not equivalent to overloaded operator expressions. There is a sequence point at the binding of values to function arguments, which makes the operator++() versions well-defined. But that doesn't exist for the native-type case.

In all four cases, i changes twice within the full-expression. Since no ,, ||, or && appear in the expressions, that's instant 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.

Edit for C++0x (updated)

§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.

Note however that a value computation and a side effect are two distinct things. If ++i is equivalent to i = i+1, then + is the value computation and = is the side effect. From 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.

So although the value computations are more strongly sequenced in C++0x than C++03, the side effects are not. Two side effects in the same expression, unless otherwise sequenced, produce UB.

Value computations are ordered by their data dependencies anyway and, side effects absent, their order of evaluation is unobservable, so I'm not sure why C++0x goes to the trouble of saying anything, but that just means I need to read more of the papers by Boehm and friends wrote.

Edit #3:

Thanks Johannes for coping with my laziness to type "sequenced" into my PDF reader search bar. I was going to bed and getting up on the last two edits anyway… right ;v) .

§5.17/1 defining the assignment operators says

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.

Also §5.3.2/1 on the preincrement operator says

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) …].

By this identity, ++ ++ x is shorthand for (x +=1) +=1. So, let's interpret that.

  • Evaluate the 1 on the far RHS and descend into the parens.
  • Evaluate the inner 1 and the value (prvalue) and address (glvalue) of x.
  • Now we need the value of the += subexpression.
    • We're done with the value computations for that subexpression.
    • The assignment side effect must be sequenced before the value of assignment is available!
  • Assign the new value to x, which is identical to the glvalue and prvalue result of the subexpression.
  • We're out of the woods now. The whole expression has now been reduced to x +=1.

So, then 1 and 3 are well-defined and 2 and 4 are undefined behavior, which you would expect.

The only other surprise I found by searching for "sequenced" in N3126 was 5.3.4/16, where the implementation is allowed to call operator new before evaluating constructor arguments. That's cool.

Edit #4: (Oh, what a tangled web we weave)

Johannes notes again that in i == ++i; the glvalue (a.k.a. the address) of i is ambiguously dependent on ++i. The glvalue is certainly a value of i, but I don't think 1.9/15 is intended to include it for the simple reason that the glvalue of a named object is constant, and cannot actually have dependencies.

For an informative strawman, consider

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

Here, the glvalue of the LHS of = is dependent on a side-effect on the prvalue of i. The address of i is not in question; the outcome of the ?: is.

Perhaps a good counterexample is

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

Here j has a glvalue distinct from (but identical to) i. Is this well-defined, yet i = ++i is not? This represents a trivial transformation that a compiler could apply to any case.

1.9/15 should say

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.

这篇关于无序值计算(a.k.a序列点)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆