C++中赋值语句的求值顺序

Order of evaluation of assignment statement in C++

map<int, int> mp;
printf("%d ", mp.size());
mp[10]=mp.size();
printf("%d\n", mp[10]);

此代码产生的答案不是很直观:

0 1

我明白为什么会这样 - 赋值的左侧 returns 引用 mp[10] 的基础值并同时创建上述值,然后才对右侧求值,使用地图的新计算 size()

这种行为在 C++ 标准的任何地方都有说明吗?或者评估顺序未定义?

结果是使用 g++ 5.2.1 获得的。

我确定标准没有指定表达式 x = y; 在 C++ 标准中计算哪个顺序 xy (这就是为什么你可以例如,不要执行 *p++ = *p++,因为 p++ 未按定义的顺序执行)。

换句话说,为了保证顺序 x = y; 在定义的顺序中,您需要将其分解为两个序列点。

 T tmp = y;
 x = tmp;

(当然,在这种特殊情况下,人们可能会认为编译器更喜欢在 size() 之前执行 operator[],因为它可以将值直接存储到 operator[] 的结果中不是将它保存在临时位置,而是在 operator[] 评估后稍后存储它 - 但我很确定编译器不需要按该顺序执行它)

来自 C++11 标准(强调我的):

5.17 Assignment and compound assignment operators

1 The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue referring to the left operand. The result in all cases is a bit-field if the left operand is a bit-field. In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.

是先计算左操作数还是先计算右操作数,语言没有规定。编译器可以自由选择首先评估任一操作数。由于您的代码的最终结果取决于操作数的评估顺序,因此我会说这是未指定的行为而不是未定义的行为。

1.3.25 unspecified behavior

behavior, for a well-formed program construct and correct data, that depends on the implementation

是的,这包含在标准中并且是未指定的行为。最近的 C++ 标准提案涵盖了这种特殊情况:N4228: Refining Expression Evaluation Order for Idiomatic C++ 旨在改进评估规则的顺序,使其在某些情况下得到很好的指定。

对这个问题的描述如下:

Expression evaluation order is a recurring discussion topic in the C++ community. In a nutshell, given an expression such as f(a, b, c), the order in which the sub-expressions f, a, b, c are evaluated is left unspecified by the standard. If any two of these sub-expressions happen to modify the same object without intervening sequence points, the behavior of the program is undefined. For instance, the expression f(i++, i) where i is an integer variable leads to undefined behavior , as does v[i] = i++. Even when the behavior is not undefined, the result of evaluating an expression can still be anybody’s guess. Consider the following program fragment:

#include <map>

int main() {
  std::map<int, int>  m;
  m[0] = m.size(); // #1
}

What should the map object m look like after evaluation of the statement marked #1? { {0, 0 } } or {{0, 1 } } ?

我们知道,除非指定,否则子表达式的计算是无序的,这来自 draft C++11 standard 部分 1.9 程序执行 说:

Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced.[...]

和所有部分 5.17 赋值和复合赋值运算符 [expr.ass] 说的是:

[...]In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.[...]

所以本节没有明确计算顺序,但我们知道这不是未定义的行为,因为 operator []size() 都是函数调用,1.9 节告诉我们( 强调我的):

[...]When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function. [ Note: Value computations and side effects associated with different argument expressions are unsequenced. —end note ] Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called function.9[...]

请注意,我在问题 Does this code from “The C++ Programming Language” 4th edition section 36.3.6 have well-defined behavior? 中介绍了 N4228 提案中的第二个有趣示例。

更新

N4228 的修订版 accepted by the Evolution Working Group at the last WG21 meeting but the paper(P0145R0) 似乎还没有。所以这在 C++17 中可能不再是未指定的。

更新 2

p0145 made this specified and update [expr.ass]p1 的修订版 3:

The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand; their result is an lvalue referring to the left operand. The result in all cases is a bit-field if the left operand is a bit-field. In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression. The right operand is sequenced before the left operand. ...

让我们看看您的代码分解成什么:

mp.operator[](10).operator=(mp.size());

这几乎说明了在第一部分中创建了 10 的条目,在第二部分中容器的大小被分配给位置 10 的整数引用。

但是现在你进入了未指定的评估顺序问题。这是一个更简单的 example

map::size() 什么时候应该被调用,在 map::operator(int const &); 之前还是之后?

没有人真正知道。