非序列值计算(aka序列点)

对不起再次打开这个话题,但想到这个话题本身已经开始给我一个未定义的行为。 想要进入明确的行为区域。

特定

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

我想到了上面的expression式(按此顺序)

 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重要的引号。

“对expression式(或子expression式)的评估通常包括值计算(包括确定左值评估对象的身份和获取先前赋值给对象进行右值评估的值)以及副作用的启动“。

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

[注:与不同参数expression式相关的值计算和副作用是不确定的。 – 注意]

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

  • 在Expr1中,关于expression式i (第一个参数)的评估,关于评估operator++(i) (它有一个副作用)是不确定的。

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

  • 在Expr2中,对expression式i (第一个参数)的评估,与评估存在operator++(i, 0) (有副作用)相反。

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

  • 在Expr3中,在调用外层operator++之前,需要完成对孤立参数operator++(i)的评估。

    因此,Expr3具有明确的行为。

  • 在Expr4中,expression式i (第一个参数)的评估关于operator[](operator++(i, 0) (其副作用))的评估是不确定的。

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

这种理解是否正确?


PS在OP中分析expression式的方法是不正确的。 这是因为,作为@Potatoswatter,注释 – “第13.6条不适用,参见13.6 / 1中的免责声明,”这些候选函数参与13.3.1.2中描述的运算符重载决策过程,并且没有其他用途。 “它们只是虚拟声明,没有关于内置操作符的函数调用语义。”

本地运算符expression式不等于重载运算符expression式。 在将值绑定到函数参数上有一个顺序点,这使得operator++()版本得到了很好的定义。 但是对于本地types的情况并不存在。

在所有四种情况下, i在全expression式中改变了两次。 由于不, || ,或&&出现在expression式中,即时UB。

§5/ 4:

在前一个和下一个序列点之间,一个标量对象应该通过评估一个expression式来最多修改其存储值。

编辑C ++ 0x(更新)

§1.9/ 15:

运算符操作数的值计算在运算符结果的值计算之前被sorting。 如果对标量对象的副作用相对于同一个标量对象的另一个副作用或者使用相同标量对象的值进行值计算而言是不确定的,则行为是不确定的。

但请注意,价值计算和副作用是两个截然不同的东西。 如果++i相当于i = i+1 ,那么+是计算值, =是副作用。 从1.9 / 12:

对expression(或子expression)的评估通常包括值计算(包括确定用于评估值的对象的身份并获取先前分配给对象的值以用于评估)和副作用的开始。

因此,尽pipeC ++ 0x中的值计算比C ++ 03更有序, 但副作用却不是。 在相同的expression中的两个副作用,除非另外测序,否则产生UB。

价值计算是由他们的数据依赖关系无论如何,副作用缺乏,他们的评价顺序是不可观测的,所以我不知道为什么C ++ 0x去说什么的麻烦,但这只是意味着我需要阅读更多Boehm和朋友的论文写道。

编辑#3:

感谢Johannes为了应付我的懒惰,在我的PDF阅读器search栏中input“sequenced”。 我正要睡觉,最后两个编辑起来……对; v)。

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

在任何情况下,赋值都是在左右操作数的值计算之后,赋值expression式的值计算之前进行sorting的。

预增量运算符的§5.3.2/ 1也是这样说的

如果x不是booltypes,则expression式++ x等效于x + = 1 [注意:请参阅… 5.7(附加)和赋值运算符(5.17)…]。

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

  • 评估远的RHS上的1 ,并下降到parens。
  • 评估x的内部1和值(prvalue)和地址(glvalue)。
  • 现在我们需要+ =子expression式的值。
    • 我们完成了该子expression式的值计算。
    • 分配的副作用必须在分配值可用之前sorting!
  • 将新值赋给x ,它与子expression式的glvalue和prvalue结果相同。
  • 我们现在已经走出困境了。 整个expression式现在已经减less到x +=1

所以,那么 1和3是明确的,2和4是不确定的行为,这是你所期望的。

通过在N3126中search“sequenced”,我发现唯一的另外一个惊喜是5.3.4 / 16,在评估构造函数参数之前允许实现调用operator new 。 这很酷。

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

约翰内斯再次注意到,在i == ++i; 我的glvalue(又名地址)模糊地依赖于++i 。 glvalue当然 i 一个值,但我不认为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应该说

如果对标量对象的副作用不是相对于同一个标量对象的另一个副作用或者是使用相同标量对象的prvalue的值计算而言的,则行为是未定义的。

在考虑如上所述的expression式时,我认为想象一个机器,其中存储器具有互锁,从而作为读取 – 修改 – 写入序列的一部分读取存储器位置将导致任何尝试读取或写入,除了最后写入在序列完成之前停止的序列。 这样的机器几乎不是一个荒谬的概念; 事实上,这样的devise可以简化许多multithreading代码场景。 另一方面,像“x = y ++;”这样的expression式 如果'x'和'y'是对同一个variables的引用,并且编译器生成的代码做了类似于read-and-lock的操作,reg1 = y; REG2 = REG1 + 1; 写x = reg1; 写和解锁y = reg2。 这将是一个非常合理的代码序列在处理器写一个新计算的值会强加stream水线延迟,但写入x将locking处理器,如果y被别名到同一个variables。