命名空间
变体
操作

求值顺序

来自 cppreference.cn
< cpp‎ | language
 
 
C++ 语言
通用主题
流程控制
条件执行语句
if
迭代语句(循环)
for
范围 for (C++11 起)
跳转语句
函数
函数声明
lambda 函数表达式
inline 说明符
动态异常规范 C++17 前已弃用*
noexcept 说明符 (C++11 起)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11 起)
auto (C++11 起)
constexpr (C++11 起)
consteval (C++20 起)
constinit (C++20 起)
存储期说明符
初始化
表达式
值类别
求值顺序
替代表示
字面量
布尔 - 整型 - 浮点
字符 - 字符串 - nullptr (C++11 起)
用户定义 (C++11 起)
实用工具
属性 (C++11 起)
类型
typedef 声明
类型别名声明 (C++11 起)
转型
内存分配
类特有函数属性
虚函数
override 说明符 (C++11 起)  
final 说明符 (C++11 起)
explicit (C++11 起)
static

特殊成员函数
模板
模板特化
形参包 (C++11 起)
杂项
 
 

任何表达式的任何部分的求值顺序,包含函数实参的求值顺序,是未指明的(以下列出一些例外)。编译器可以以任何顺序求值运算数和其他子表达式,并且在再次求值同一表达式时可以选择另一种顺序。

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());       // all 6 permutations of output are allowed
    return a() + b() + c(); // all 6 permutations of output are allowed
}

可能的输出

b
c
a
c
a 
b

目录

[编辑] “先序于”规则 (C++11 起)

[编辑] 表达式求值

每个表达式的求值包括

  • 值计算:计算表达式返回的值。这可能涉及确定对象的标识(左值求值,例如,如果表达式返回对某个对象的引用)或读取先前赋给对象的值(纯右值求值,例如,如果表达式返回数字或某些其他值)。
  • 副作用的启动:访问(读取或写入)由 volatile 左值指定的对象,修改(写入)对象,调用库 I/O 函数,或调用执行任何这些操作的函数。

[编辑] 排序

先序于是同一线程内求值 AB 之间的非对称、传递、成对关系。

  • 如果 A 先序于 B(或等效地,B 后序于 A),则 A 的求值将在 B 的求值开始之前完成。
  • 如果 A 不先序于 BB 先序于 A,则 B 的求值将在 A 的求值开始之前完成。
  • 如果 A 不先序于 BB 也不先序于 A,则存在两种可能性
    • AB 的求值是无序的:它们可以以任何顺序执行并且可能重叠(在单个执行线程中,编译器可能会交错构成 AB 的 CPU 指令)。
    • A 和 B 的求值是不定序的:它们可以以任何顺序执行,但可能不重叠:A 将在 B 之前完成,或者 B 将在 A 之前完成。下次求值同一表达式时,顺序可能相反。

如果与表达式 X 关联的每个值计算和每个副作用都先序于与表达式 Y 关联的每个值计算和每个副作用,则称表达式 X 先序于表达式 Y

[编辑] 规则

1) 每个完整表达式都先序于下一个完整表达式。
2) 任何运算符的运算数的值计算(但不是副作用)先序于运算符结果的值计算(但不是其副作用)。
3) 当调用函数 func(无论该函数是否为内联,以及是否使用了显式函数调用语法)时,以下列表中的每一项都先序于下一项
  • 每个实参表达式和指定 func 的后缀表达式
(C++26 起)
  • func 主体中的每个表达式或语句
(C++26 起)
4) 内建的后自增和后自减运算符的值计算先序于其副作用。
5) 内建的前自增和前自减运算符的副作用先序于其值计算(由于定义为复合赋值而产生的隐式规则)。
6) 内建逻辑与运算符 &&、内建逻辑或运算符 || 和内建逗号运算符 , 的第一个(左)运算数先序于第二个(右)运算数。
7) 条件运算符 ?: 中的第一个运算数先序于第二个或第三个运算数。
8) 内建赋值运算符和所有内建复合赋值运算符的副作用(左实参的修改)后序于左右实参的值计算(但不是副作用),并且先序于赋值表达式的值计算(即,在返回对已修改对象的引用之前)。
9)列表初始化中,给定初始化子句的每个值计算和副作用都先序于与花括号包围的逗号分隔的初始化器列表中的任何后续初始化子句关联的每个值计算和副作用。
10) 未先序于或后序于函数外部的另一个表达式求值(可能是另一个函数调用)的函数调用,相对于该求值是不定序的(程序必须表现为 如同 构成函数调用的 CPU 指令未与构成其他表达式求值的指令交错,包括其他函数调用,即使函数是内联的)。
规则 10 有一个例外:由在 std::execution::par_unseq 执行策略下执行的标准库算法进行的函数调用是无序的,并且可能彼此任意交错。 (C++17 起)
11) 对分配函数 (operator new) 的调用 相对于 new 表达式中构造函数实参的求值是不定序的(C++17 前)先序于 new 表达式中构造函数实参的求值(C++17 起)
12) 从函数返回时,作为函数调用结果的临时对象的复制初始化先序于 return 语句 的运算数末尾的所有临时对象的析构,而后者又先序于包围 return 语句 的块的局部变量的析构。
13) 在函数调用表达式中,命名函数的表达式先序于每个实参表达式和每个默认实参。
14) 在函数调用中,每个形参的初始化的值计算和副作用相对于任何其他形参的值计算和副作用是不定序的。
15) 每个重载运算符在使用运算符表示法调用时,都服从它所重载的内建运算符的排序规则。
16) 在下标表达式 E1[E2] 中,E1 先序于 E2
17) 在指向成员的指针表达式 E1.*E2E1->*E2 中,E1 先序于 E2(除非 E1 的动态类型不包含 E2 引用的成员)。
18) 在移位运算符表达式 E1 << E2E1 >> E2 中,E1 先序于 E2
19) 在每个简单赋值表达式 E1 = E2 和每个复合赋值表达式 E1 @= E2 中,E2 先序于 E1
20) 圆括号括起的初始化器中的逗号分隔的表达式列表中的每个表达式的求值都如同函数调用(不定序)。
(C++17 起)

[编辑] 未定义行为

在以下情况下,行为是未定义的

1)内存位置的副作用相对于对同一内存位置的另一个副作用是无序的
i = ++i + 2;       // well-defined
i = i++ + 2;       // undefined behavior until C++17
f(i = -2, i = -2); // undefined behavior until C++17
f(++i, ++i);       // undefined behavior until C++17, unspecified after C++17
i = ++i + i++;     // undefined behavior
2) 对内存位置的副作用相对于使用同一内存位置中任何对象的值的值计算是无序的
cout << i << i++; // undefined behavior until C++17
a[i] = i++;       // undefined behavior until C++17
n = ++i + i;      // undefined behavior
3) 在内存位置中启动或结束对象的生存期相对于以下任何操作是无序的
  • 对同一内存位置的副作用
  • 使用同一内存位置中任何对象的值的值计算
  • 启动或结束占用与内存位置重叠的存储的对象生存期
union U { int x, y; } u;
(u.x = 1, 0) + (u.y = 2, 0); // undefined behavior

[编辑] 顺序点规则 (C++11 前)

[编辑] C++11 前的定义

表达式求值可能会产生副作用,这些副作用是:访问由 volatile 左值指定的对象,修改对象,调用库 I/O 函数,或调用执行任何这些操作的函数。

顺序点是执行序列中的一个点,在该点上,序列中先前求值的所有副作用都已完成,并且后续求值的副作用尚未开始。

[编辑] C++11 前的规则

1) 在每个完整表达式的末尾(通常在分号处)有一个顺序点。
2) 当调用函数时(无论该函数是否为内联以及是否使用了函数调用语法),在所有函数实参(如果有)的求值之后,函数主体中的任何表达式或语句的执行之前,存在一个顺序点。
3) 从函数返回时,在函数调用结果的复制初始化之后,以及在 return 语句 中的 expression 末尾的所有临时对象的析构之前(如果有),存在一个顺序点。
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++ 标准。

DR 应用于 已发布行为 正确行为
CWG 1885 C++11 函数返回时自动变量的析构排序
不明确
添加排序规则
CWG 1949 C++11 使用了“后序于”,但在 C++ 标准中未定义 定义为
“先序于”的逆
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 文档 关于 求值顺序