13

C++11 标准(5.17, expr.ass) 指出

在所有情况下,赋值都在左右操作数的值计算之后和赋值表达式的值计算之前进行排序。对于不确定顺序的函数调用,复合赋值的操作是单次求值

据我了解,作为给定赋值一部分的所有表达式都将在赋值本身之前进行评估。即使我在同一个赋值中修改同一个变量两次,这条规则也应该起作用,我相当肯定,这在以前是未定义的行为。

将给定的代码:

int a = 0;
a = (a+=1) = 10;

if ( a == 10 ) {
    printf("this is defined");
} else {
    printf("undefined"); 
}

总是评估为a==10?

4

3 回答 3

8

是的,C++98 和 C++11 之间发生了变化。我相信您的示例在 C++11 规则下定义良好,同时在 C++98 规则下表现出未定义的行为。

作为一个更简单的示例,x = ++x;在 C++98 中未定义,但在 C++11 中定义良好。请注意,x = x++;它仍然是未定义的(后增量的副作用与表达式的评估无关,而预增量的副作用在相同之前排序)。

于 2013-10-25T16:34:35.037 回答
7

让我们将您的代码重写为

E1 = (E2 = E3)

其中 E1 是表达式a,E2 是表达式a += 1,E3 是表达式10。在这里,我们使用了赋值运算符从右到左分组(C++11 标准中的第 5.17/1 节)。

§5.17/1 还指出:

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

将此应用于我们的表达式意味着我们首先必须评估子表达式E1E2 = E3。请注意,这两个评估之间没有“sequenced-before”关系,但这不会导致任何问题。

id-expression 的评估E1是微不足道的(结果就是a它本身)。赋值表达式 的求值过程E2 = E3如下:

首先必须评估两个子表达式。文字 的评估E3又是微不足道的(给出值 10 的纯右值)。

(复合)赋值表达式 E2的评估按以下步骤完成:

1) 的行为a += 1等价于a = a + 1a只评估一次 (§5.17/7)。在评估子表达式a1(以任意顺序)之后,应用左值到右值的转换a以读取存储在a.

2) a(which is 0) 和 of的值1被相加 ( a + 1) 并且这个相加的结果是 value 的纯右值1

3) 在我们计算赋值结果之前a = a + 1,左操作数所指的对象的值被右操作数的值替换(第 5.17/2 节)。的结果E2是一个引用新值的左值1。请注意,副作用(更新左操作数的值)在赋值表达式的值计算之前排序。这是上面引用的§5.17/1。

现在我们已经评估了子表达式E2E3,表达式所E2引用的值被替换为 的值E3,即10。因此 的结果E2 = E3是 value 的左值10

最后,表达式E1引用的值被替换为表达式的值E2 = E3,我们计算为10。因此,变量a最终包含 value 10

由于所有这些步骤都是明确定义的,因此整个表达式会产生明确定义的值。

于 2013-10-30T12:54:25.620 回答
4

在做了一些研究之后,我确信您的代码行为在 C++11 中得到了很好的定义。

$1.9/15 状态:

运算符的操作数的值计算在运算符结果的值计算之前排序。

$5.17/1 指出:

赋值运算符 ( =) 和复合赋值运算符都从右到左分组。

如果我理解正确,在你的例子中

a = (a+=1) = 10;

这意味着 和 的值计算(a+=1)必须10在 的 值计算之前进行,(a+=1) = 10并且该表达式的值计算必须在a = (a+=1) = 10;评估之前完成。

$5.17/1 指出:

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

这意味着赋值必须发生在值计算之前,因此,由于传递性,评估(a+=1) = 10只能在赋值之后开始a+=1(因为它的值只能在副作用之后计算)。

第二个和第三个任务也是如此。

另请参阅这个出色的答案,它比我更详细和更好地解释了先排序关系。

于 2013-10-29T22:34:32.830 回答