关于c ++:未测序的值计算(a.k.a序列点)

Unsequenced value computations (a.k.a sequence points)

很抱歉再次打开这个主题,但是思考这个主题本身已经开始给我一个未定义的行为。希望进入行为定义良好的区域。

鉴于

1
2
3
4
5
6
int i = 0;
int v[10];
i = ++i;     //Expr1
i = i++;     //Expr2
++ ++i;      //Expr3
i = v[i++];  //Expr4

我把上面的表达(按这个顺序)看作

1
2
3
4
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++的重要引文。

$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的评价(第一个论点)与对expression operator++(i)的评价(有副作用)没有排序。

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

  • 在expr2中,表达式i的计算(第一个参数)与表达式operator++(i, 0)的计算(有副作用)没有排序。

    因此,expr2具有未定义的行为。

  • 在expr3中,在调用外部operator++之前,需要完成对单个参数operator++(i)的评估。

    因此expr3具有定义良好的行为。

  • 在expr4中,对表达式EDOCX1(第一个参数)的评估与对EDOCX1(第7个参数)的评估(具有副作用)没有排序。

    因此,expr4具有未定义的行为。

这种理解正确吗?

P.S.分析OP中表达式的方法不正确。这是因为,正如@potatoswatter,注释-"第13.6条不适用。参见13.6/1中的免责声明,"这些候选函数参与了13.3.1.2中所述的运算符重载解决过程,并且没有用于其他目的。"它们只是一个虚拟声明;对于内置运算符不存在函数调用语义。


本机运算符表达式不等价于重载的运算符表达式。在值与函数参数的绑定上有一个序列点,这使得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.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和朋友写道。

编辑第3页:

感谢Johannes处理我的懒惰,在我的PDF阅读器搜索栏中输入"sequenced"。不管怎样,我要上床睡觉,然后开始最后两次编辑……对吧;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的缩写。所以,让我们来解释一下。

  • 在远端右侧评估1,然后下降到Parens。
  • 评估1的内部值和x的值(prvalue)和地址(glvalue)。
  • 现在我们需要+=子表达式的值。
    • 我们已经完成了该子表达式的值计算。
    • 在赋值值可用之前,必须对赋值副作用进行排序!
  • 将新值赋给x,与子表达式的glvalue和prvalue结果相同。
  • 我们现在不在森林里了。现在整个表达已经减少到x +=1

所以,1和3是定义良好的,2和4是未定义的行为,这是您所期望的。

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

编辑4:(哦,我们编织的网多乱啊)

约翰内斯再次指出,在i == ++i;中,i的gl值(也就是地址)在很大程度上依赖于++i。glvalue当然是i的值,但我不认为1.9/15是为了包含它,因为命名对象的glvalue是常量,实际上不能有依赖性。

对于信息丰富的策略,请考虑

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

这里,=的lhs的gl值取决于i的pr值的副作用。i的地址没有问题;?:的结果是。

或许一个很好的反例是

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

这里,j的gl值不同于(但与)i的gl值。这是一个定义明确的问题吗,但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.


在考虑类似上面提到的表达式时,我发现可以想象一台机器,其中内存具有互锁,因此将内存位置作为读-修改-写序列的一部分进行读取将导致除序列的结束写入之外的任何尝试读取或写入暂停,直到序列完成。这样的一台机器几乎不会是一个荒谬的概念;事实上,这样的设计可以简化许多多线程代码场景。另一方面,如果"x"和"y"是对同一个变量的引用,那么类似"x=y++;"的表达式在这样的机器上可能会失败,并且编译器生成的代码执行了诸如read和lock reg1=y、reg2=reg1+1、write x=reg1、write和unlock y=reg2之类的操作。在处理器上,这将是一个非常合理的代码序列,其中写入新计算的值将导致管道延迟,但如果Y别名为同一个变量,则对X的写入将锁定处理器。