Namespaces
Variants

Order of evaluation

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Value categories
Order of evaluation
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

任何表达式中任何部分的求值顺序,包括函数参数的求值顺序都是 未指定的 (除下列特例外)。编译器可以按任意顺序对操作数及其他子表达式进行求值,且在再次对同一表达式求值时可能选择不同的顺序。

C++中不存在从左到右或从右到左求值的概念。这不应与运算符从左到右和从右到左的结合性混淆:表达式 a ( ) + b ( ) + c ( ) 由于 operator + 的左结合性被解析为 ( a ( ) + b ( ) ) + c ( ) ,但在运行时 c ( ) 可能首先、最后或在 a ( ) b ( ) 之间被求值:

#include <cstdio>
int a() { return std::puts("a"); }
int b() { return std::puts("b"); }
int c() { return std::puts("c"); }
void z(int, int, int) {}
int main()
{
    z(a(), b(), c());       // 允许所有6种输出排列
    return a() + b() + c(); // 允许所有6种输出排列
}

可能的输出:

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

规则

1) 每个 完整表达式 都先序于下一个完整表达式执行。
2) 任何 运算符 的操作数的值计算(但不包括副作用)在运算符结果的值计算(但不包括其副作用)之前被定序。
3) 当调用函数 func 时(无论函数是否为内联函数,也无论是否使用显式函数调用语法),以下列表中的每个项目都在下一个项目之前按顺序执行:
  • 所有参数表达式以及指定 func 的后缀表达式
(since C++26)
  • func 函数体中的所有表达式或语句
(since C++26)
4) 内置 后置递增与后置递减 运算符的值计算在其副作用之前被定序。
5) 内置 前置递增与前置递减 运算符的副作用在其值计算之前被定序(根据复合赋值的定义形成的隐式规则)。
6) 内置 逻辑 与运算符 && 的第一个(左)操作数、内置逻辑或运算符 || 以及内置 逗号运算符 , 均在第二个(右)操作数之前完成求值顺序。
7) 条件运算符 ?: 中的第一个操作数在第二个或第三个操作数之前被定序。
8) 内置 赋值 运算符及所有内置 复合 赋值运算符的副作用(对左参数的修改)被定序在左右参数的值计算(但不包括副作用)之后,并被定序在赋值表达式的值计算之前(即在返回被修改对象的引用之前)。
9) 列表初始化 中,给定初始化子句的每个值计算和副作用,都按顺序先于出现在花括号包围的、以逗号分隔的初始化器列表中其后的任何初始化子句相关的值计算和副作用。
10) 一个函数调用若不在另一个函数外表达式求值(可能是另一个函数调用)之前或之后定序,则相对于该求值属于不确定顺序(程序必须表现得 如同 构成函数调用的CPU指令未与其他表达式(包括其他函数调用)的求值指令交错执行,即使函数被内联)。
规则10存在一个例外:在 std::execution::par_unseq 执行策略下运行的标准库算法所执行的函数调用是无序的,可能以任意方式相互交错执行。 (since C++17)
11) 对分配函数( operator new )的调用在 new 表达式 与构造函数参数的求值相对顺序不确定 (C++17 前) 先序于构造函数参数的求值 (C++17 起)
12) 从函数返回时,作为函数调用求值结果的临时对象的复制初始化,在 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)

未定义行为

以下情况下的行为是 未定义 的:

1) 对同一 内存位置 的副作用相对于另一个副作用是无序的:
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++;     // 未定义行为
2) 对内存位置的副作用相对于使用同一内存位置中任何对象值的值计算是无序的:
cout << i << i++; // 直到 C++17 前是未定义行为
a[i] = i++;       // 直到 C++17 前是未定义行为
n = ++i + i;      // 未定义行为
3) 在内存位置中开始或结束对象的 生存期 ,相对于以下任意操作都是无顺序的:
  • 对同一内存位置的副作用
  • 使用同一内存位置中任何对象值的值计算
  • 开始或结束占据与该内存位置重叠的存储空间的对象的生存期
union U { int x, y; } u;
(u.x = 1, 0) + (u.y = 2, 0); // undefined behavior

序列点规则 (直至 C++11)

C++11 前定义

表达式的求值可能产生副作用,包括:访问由易失左值指定的对象、修改对象、调用库I/O函数,或调用执行上述任意操作的函数。

一个 序列点 是执行序列中的一个点,在该点处序列中之前所有求值的副作用均已完成,而后续求值的副作用尚未开始。

C++11 前规则

1) 在每个 完整表达式 的末尾存在一个序列点(通常位于分号处)。
2) 当调用函数时(无论该函数是否为内联函数,也无论是否使用了函数调用语法),在所有函数参数(若有)求值之后、函数体内任何表达式或语句执行之前,存在一个顺序点。
3) 从函数返回时,在函数调用结果的复制初始化之后、 return 语句 末尾(若存在)所有临时对象销毁之前存在一个序列点。
4) 在函数返回值的复制完成之后、在函数外部任何表达式执行之前,存在一个序列点。
5) 一旦函数开始执行,在调用函数执行完成之前,调用方函数中的任何表达式都不会被求值(函数不能交错执行)。
6) 在以下四个表达式的求值过程中,使用内置(非重载)运算符时,在表达式 a 的求值之后存在一个序列点。
a && b
a || b
a ? b : c
a , b

C++11 前未定义行为

以下情况下的行为是 未定义 的:

1) 在前一个与后一个序列点之间,若通过表达式求值对同一内存位置中的对象值进行多次修改:
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
2) 在前一个与后一个序列点之间,对于其值被表达式求值所修改的对象,若以除确定将要存储的值之外的方式访问其先前值:
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]

参见

C 文档 关于 求值顺序