在 C++11 中的一个表达式中对同一变量进行双重赋值

Double assignment of the same variable in one expression in C++11

本文关键字:变量 赋值 一个 C++11 表达式      更新时间:2023-10-16

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

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

举个更简单的例子,x = ++x;在 C++98 中未定义,但在 C++11 中定义良好。请注意,x = x++;仍未定义(后增量的副作用在表达式的评估中未排序,而前增量的副作用在相同之前排序)。

让我们将代码重写为

E1 = (E2 = E3)

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

§5.17/1 此外指出:

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

将其应用于我们的表达式意味着我们首先必须计算子表达式 E1E2 = E3 。请注意,这两个评估之间没有"之前排序"的关系,但这不会造成任何问题。

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

首先,必须计算两个子表达式。对文字E3的评估同样是微不足道的(给出值为 10 的 prvalue)。

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

1) a += 1的行为等同于a = a + 1a只被评估一次(§5.17/7)。在计算子表达式 a1(以任意顺序)后,将左值到重值转换应用于a,以便读取存储在 a 中的值。

2)a0)和1的值相加(a + 1),这种相加的结果是值1的prvalue。

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

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

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

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

经过一些研究,我相信你的代码行为在第 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之后开始(因为它的值可能只在副作用之后计算)。

第二次和第三次作业也是如此。

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