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

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

特定

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 / 15-“如果标量对象的副作用不是相对于同一个标量对象的另一个副作用使用相同标量对象的值计算的值,则行为是未定义的。”

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

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

  • 在Expr1中,对表达式i (第一个参数)的评估在评估演算operator++(i) (它有副作用)方面是不确定的。

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

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

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

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

    因此Expr3具有明确的行为。

  • 在Expr4中,表达式i (第一个参数)的评估与operator[](operator++(i, 0) (它有副作用))的评估相关。

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

  • 这种理解是否正确?


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


    本地运算符表达式不等于重载的运算符表达式。 在值绑定到函数参数上有一个序列点,这使得operator++()版本的定义更加明确。 但这对于本地类型的情况并不存在。

    在所有四种情况下, i在全表达式中改变了两次。 既然没有,|| ,或者&&出现在表达式中,即时UB。

    §5/ 4:

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

    编辑C ++ 0x(更新)

    §1.9/ 15:

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

    但请注意,值计算和副作用是两个截然不同的事情。 如果++i等于i = i+1 ,那么+是计算值,而=是副作用。 从1.9 / 12:

    对表达式(或子表达式)的评估通常包括值计算(包括确定对于glvalue评估的对象的身份并获取先前分配给对象以用于评估值)和副作用的启动。

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

    价值计算无论如何都依赖于它们的数据依赖性,并且副作用不存在,它们的评估顺序是不可观测的,所以我不确定C ++ 0x为什么要说什么,但这意味着我需要阅读更多Boehm和朋友的论文写道。

    编辑#3:

    感谢Johannes为了应对我的懒惰,在我的PDF阅读器搜索栏中输入“sequenced”。 无论如何,我正要睡觉,并且最后进行了两次编辑......对; v)。

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

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

    预增量运算符的§5.3.2/ 1也表示

    如果x不是bool类型,则表达式++ x等效于x + = 1 [注意:请参阅... addition(5.7)和赋值运算符(5.17)...]。

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

  • 在遥远的RHS上评估1 ,然后进入对角线。
  • 评估x的内部1和值(prvalue)和地址(glvalue)。
  • 现在我们需要+ =子表达式的值。
  • 我们完成了该子表达式的值计算。
  • 分配的副作用必须在分配值可用之前排序!
  • 将新值赋给x ,它与子表达式的glvalue和prvalue结果相同。
  • 我们现在摆脱困境了。 整个表达式现在已经减少到x +=1
  • 所以,那么 1和3是明确定义的,2和4是未定义的行为,这是你所期望的。

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

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

    约翰内斯再次指出,在i == ++i; 的glvalue(又名地址) i是隐约依赖++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进行值计算而言是不确定的,则行为是未定义的。


    在考虑如上所述的表达式时,我认为想象一台机器,其中内存具有互锁性,因此作为读取 - 修改 - 写入序列的一部分读取内存位置将导致任何尝试读取或写入,除了最后写入该序列将被暂停,直到序列完成。 这样的机器不会是一个荒谬的概念; 事实上,这样的设计可以简化许多多线程代码场景。 另一方面,像“x = y ++;”这样的表达式 如果'x'和'y'是对同一个变量的引用,并且编译器生成的代码做了类似于读取和锁定的操作,reg1 = y; REG2 = REG1 + 1; 写x = reg1; 写和解锁y = reg2。 这是处理器上的一个非常合理的代码序列,其中写入新计算的值会强制执行流水线延迟,但如果y被别名为相同变量,写入x将锁定处理器。

    链接地址: http://www.djcxy.com/p/12731.html

    上一篇: Unsequenced value computations (a.k.a sequence points)

    下一篇: Multiple preincrement operations on a variable in C++(C ?)