Order of evaluation
任何表达式中任何部分的求值顺序,包括函数参数的求值顺序都是 未指定的 (除下列特例外)。编译器可以按任意顺序对操作数及其他子表达式进行求值,且在再次对同一表达式求值时可能选择不同的顺序。
C++中不存在从左到右或从右到左求值的概念。这不应与运算符从左到右和从右到左的结合性混淆:表达式 a ( ) + b ( ) + c ( ) 由于 operator + 的左结合性被解析为 ( a ( ) + b ( ) ) + c ( ) ,但在运行时 c ( ) 可能首先、最后或在 a ( ) 与 b ( ) 之间被求值:
可能的输出:
b c a c a b
目录 |
"Sequenced before" 规则 (自 C++11 起)
表达式求值
每个表达式的求值包括:
- 值计算 :计算表达式返回的数值。这可能涉及确定对象的身份(左值求值,例如当表达式返回某个对象的引用时)或读取先前赋给对象的值(纯右值求值,例如当表达式返回数字或其他值时)。
- 副作用 的启动:访问(读取或写入)由易变性左值指定的对象、修改(写入)对象、调用库I/O函数,或调用执行任何上述操作的函数。
求值顺序
Sequenced before
是一种在同一线程内的求值操作
A
与
B
之间不对称、可传递的成对关系。
-
若
A先序于B(或等价表述为B后序于A),则A的求值将在B的求值开始前完成。 -
若
A不先序于B且B先序于A,则B的求值将在A的求值开始前完成。 -
若
A不先序于B且B不先序于A,则存在两种可能:-
A与B的求值处于 无序列关系 :它们可按任意顺序执行且可能重叠(在单一线程内,编译器可交错执行组成A与B的CPU指令)。 -
A与B的求值处于 不确定序列关系 :它们可按任意顺序执行但不可重叠:要么A在B之前完成,要么B在A之前完成。下次对同一表达式求值时顺序可能相反。
-
若表达式 X 的每个值计算和所有副作用均先序于表达式 Y 的每个值计算和所有副作用,则称 X 先序于 Y 。
规则
- 所有参数表达式以及指定 func 的后缀表达式
|
(since C++26) |
- func 函数体中的所有表达式或语句
|
(since C++26) |
| 规则10存在一个例外:在 std::execution::par_unseq 执行策略下运行的标准库算法所执行的函数调用是无序的,可能以任意方式相互交错执行。 | (since C++17) |
return
语句操作数末尾所有临时对象的销毁之前进行排序,而该销毁又在包围
return
语句的块中局部变量的销毁之前进行排序。
|
13)
在函数调用表达式中,命名函数的表达式先序于每个实参表达式和每个默认参数。
14)
在函数调用中,每个形参初始化的值计算和副作用相对于其他任意形参的值计算和副作用都是 indeterminately sequenced。
15)
每个重载运算符在使用运算符表示法调用时,遵循其重载的内置运算符的定序规则。
16)
在下标表达式
E1
[
E2
]
中,
E1
先序于
E2
。
17)
在成员指针表达式
E1.
*
E2
或
E1
-
>
*
E2
中,
E1
先序于
E2
(除非
E1
的动态类型不包含
E2
所引用的成员)。
18)
在移位运算符表达式
E1
<<
E2
和
E1
>>
E2
中,
E1
先序于
E2
。
19)
在每个简单赋值表达式
E1
=
E2
和每个复合赋值表达式
E1 @
=
E2
中,
E2
先序于
E1
。
20)
括号初始化器中逗号分隔的表达式列表中的每个表达式都如同函数调用一样求值(indeterminately-sequenced)。
|
(since C++17) |
未定义行为
以下情况下的行为是 未定义 的:
i = ++i + 2; // 良好定义 i = i++ + 2; // C++17 前为未定义行为 f(i = -2, i = -2); // C++17 前为未定义行为 f(++i, ++i); // C++17 前为未定义行为,C++17 后为未指明行为 i = ++i + i++; // 未定义行为
cout << i << i++; // 直到 C++17 前是未定义行为 a[i] = i++; // 直到 C++17 前是未定义行为 n = ++i + i; // 未定义行为
- 对同一内存位置的副作用
- 使用同一内存位置中任何对象值的值计算
- 开始或结束占据与该内存位置重叠的存储空间的对象的生存期
union U { int x, y; } u; (u.x = 1, 0) + (u.y = 2, 0); // undefined behavior
序列点规则 (直至 C++11)
C++11 前定义
表达式的求值可能产生副作用,包括:访问由易失左值指定的对象、修改对象、调用库I/O函数,或调用执行上述任意操作的函数。
一个 序列点 是执行序列中的一个点,在该点处序列中之前所有求值的副作用均已完成,而后续求值的副作用尚未开始。
C++11 前规则
a && b a || b a ? b : c a , b
C++11 前未定义行为
以下情况下的行为是 未定义 的:
i = ++i + i++; // undefined behavior i = i++ + 1; // undefined behavior i = ++i + 1; // undefined behavior ++ ++i; // undefined behavior f(++i, ++i); // undefined behavior f(i = -1, i = -1); // undefined behavior
cout << i << i++; // undefined behavior a[i] = i++; // undefined behavior
缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的C++标准。
| 缺陷报告 | 适用标准 | 发布时行为 | 修正后行为 |
|---|---|---|---|
| CWG 1885 | C++11 | 函数返回时自动变量析构的时序关系未明确说明 | 添加了时序规则 |
| CWG 1949 | C++11 | C++标准中使用但未定义“sequenced after”关系 | 定义为“sequenced before”的逆关系 |
| CWG 1953 | C++11 |
涉及内存位置的副作用和值计算可能相对于
同一内存位置对象的生命周期开始或结束 是无序的 |
此情况下行为未定义 |
| CWG 2146 | C++98 | 涉及未定义行为的情形未考虑位域 | 已考虑位域情况 |
参考文献
- C++23 标准 (ISO/IEC 14882:2024):
-
- 6.9.1 程序执行 [intro.execution]
-
- 7.6.1.6 自增与自减 [expr.post.incr]
-
- 7.6.2.8 new 运算符 [expr.new]
-
- 7.6.14 逻辑与运算符 [expr.log.and]
-
- 7.6.15 逻辑或运算符 [expr.log.or]
-
- 7.6.16 条件运算符 [expr.cond]
-
- 7.6.19 赋值与复合赋值运算符 [expr.ass]
-
- 7.6.20 逗号运算符 [expr.comma]
-
- 9.4.5 列表初始化 [dcl.init.list]
- C++20 标准 (ISO/IEC 14882:2020):
-
- 6.9.1 程序执行 [intro.execution]
-
- 7.6.1.5 自增与自减 [expr.post.incr]
-
- 7.6.2.7 new 运算符 [expr.new]
-
- 7.6.14 逻辑与运算符 [expr.log.and]
-
- 7.6.15 逻辑或运算符 [expr.log.or]
-
- 7.6.16 条件运算符 [expr.cond]
-
- 7.6.19 赋值与复合赋值运算符 [expr.ass]
-
- 7.6.20 逗号运算符 [expr.comma]
-
- 9.4.4 列表初始化 [dcl.init.list]
- C++17 标准 (ISO/IEC 14882:2017):
-
- 4.6 程序执行 [intro.execution]
-
- 8.2.6 自增与自减 [expr.post.incr]
-
- 8.3.4 new 运算符 [expr.new]
-
- 8.14 逻辑与运算符 [expr.log.and]
-
- 8.15 逻辑或运算符 [expr.log.or]
-
- 8.16 条件运算符 [expr.cond]
-
- 8.18 赋值与复合赋值运算符 [expr.ass]
-
- 8.19 逗号运算符 [expr.comma]
-
- 11.6.4 列表初始化 [dcl.init.list]
- C++14 标准 (ISO/IEC 14882:2014):
-
- 1.9 程序执行 [intro.execution]
-
- 5.2.6 递增与递减 [expr.post.incr]
-
- 5.3.4 new 运算符 [expr.new]
-
- 5.14 逻辑与运算符 [expr.log.and]
-
- 5.15 逻辑或运算符 [expr.log.or]
-
- 5.16 条件运算符 [expr.cond]
-
- 5.17 赋值与复合赋值运算符 [expr.ass]
-
- 5.18 逗号运算符 [expr.comma]
-
- 8.5.4 列表初始化 [dcl.init.list]
- C++11 标准 (ISO/IEC 14882:2011):
-
- 1.9 程序执行 [intro.execution]
-
- 5.2.6 自增与自减 [expr.post.incr]
-
- 5.3.4 new 运算符 [expr.new]
-
- 5.14 逻辑与运算符 [expr.log.and]
-
- 5.15 逻辑或运算符 [expr.log.or]
-
- 5.16 条件运算符 [expr.cond]
-
- 5.17 赋值与复合赋值运算符 [expr.ass]
-
- 5.18 逗号运算符 [expr.comma]
-
- 8.5.4 列表初始化 [dcl.init.list]
参见
- Operator precedence 定义了表达式如何从其源代码表示形式构建而成。
|
C 文档
关于
求值顺序
|