【问题标题】:Unsequenced value computations (a.k.a sequence points)未排序的值计算(a.k.a 序列点)
【发布时间】:2011-04-20 15:32:05
【问题描述】:

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

给定

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-“表达式的求值 (或子表达式)一般 包括价值计算 (包括确定身份 用于左值评估的对象和 获取先前分配给的值 用于右值评估的对象)和 副作用的开始。”

$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 具有未定义的行为。

这种理解正确吗?


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

【问题讨论】:

  • +!:好问题。我会留意答案。
  • @Chubsdad :我同意@James McNellis 在他的回答中所说的(他后来删除了)。所有 4 个表达式都在 C++0x [恕​​我直言] 中调用 UB。我认为你应该在 csc++ (comp.std.c++) 上问这个问题。 :)
  • @Prasoon Saurav:为什么 Expr3 有未定义的行为?我认为这应该没问题。 gcc/comeau/llvm(demo) 也全部编译,没有任何警告。
  • 那是因为与 ++ [inner] 和 ++ [outer] 相关的副作用不是相对于彼此排序的(尽管值计算是排序的)。 :)
  • 查看this。有人提到Some more complicated cases are not diagnosed by -Wsequence-point option, and it may give an occasional false positive result,......

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


【解决方案1】:

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

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

§5/4:

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

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

§1.9/15:

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

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

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

因此,尽管 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 [注意:请参阅……加法 (5.7) 和赋值运算符 (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 应该说

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

【讨论】:

【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多