求值顺序

来自cppreference.com
< cpp‎ | language

几乎所有 C++ 运算符的求值顺序(包括函数调用表达式中的函数参数求值顺序和任何表达式中子表达式的求值顺序)都是未指定的。编译器能以任何顺序求值,并可以在再次求值相同表达式时选择另一顺序。

此规则有例外,注于下方。

除了下方所注者, C++ 无从左到右或从右到左求值的概念。不要将此与运算符的从左到右与从右到左结合性混淆:表达式 f1() + f2() + f3() 由于 operator+ 的从左到右结合性分析为 (f1() + f2()) + f3() ,但运行时对 f3 的函数调用可能首先、最后,或在 f1()f2() 之间求值。

目录

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

[编辑] 定义

[编辑] 表达式求值

每个表达式的求值包括:

  • 值计算:计算表达式所返回的值。这可能涉及确定对象的身份(泛左值求值,例如若表达式返回到某对象的引用)或读取先前赋给对象的值(纯右值求值,例如若表达式返回数或某个其他值)
  • 副效应实例化:访问(读或写) volatile 泛左值所指代的对象,修改(写入)对象、调用库 I/O 函数或调用任何做这些操作的函数。

[编辑] 顺序

“先序于 (sequenced-before) ”是同一线程中的求值的反对称、传递的成对关系。

  • 若 A 先序于 B ,则 A 的求值将在 B 的求值开始前完成。
  • 若 A 不先序于 B 而 B 先序于 A ,则 B 的求值将在 A 的求值开始前完成。
  • 若 A 不先序于 B 而 B 不先序于 A ,则存在二种可能:
    • A 与 B 的求值无顺序 (unsequenced) :它们能以任何顺序进行并可能重叠(在同一执行线程内,编译器可以将组成 A 与 B 的 CPU 指令交错)
    • A 与 B 的求值是非确定顺序 (indeterminately sequenced) 的:它们可以任意顺序进行但不可重叠, A 将在 B 前完成,或 B 将在 A 前完成。下次求值相同表达式时顺序可以相反。

[编辑] 规则

1) 每个下列完整表达式的值计算和副效应
(C++20 起)
  • 整个初始化器,包含任何逗号分隔的组成表达式
  • 在非临时对象生存期末尾生成的析构函数调用
  • 不是另一完整表达式一部分的表达式(例如整个表达式语句for/while 循环的控制表达式、 if/switch 的控制表达式、 return 语句中的表达式等),
包括应用到表达式结果的隐式转换、对临时量的析构函数调用、默认成员初始化器(初始化聚合体时)和任何涉及函数调用的语言构造,都先序于下个完整表达式的每个值计算和副效应。
2) 任何运算符的运算数的值计算(但非副效应)先序于运算符结果的值计算(但非副效应)。
3) 调用函数时(无论函数是否内联,无论是否使用显式函数调用语法),与任何参数表达式或与指代被调用函数的后缀表达式关联的每个值计算和副效应,都先序于被调用函数体内的每个表达式或语句的求值。
4) 内建后自增与后自减运算符的值计算先序于其副效应。
5) 内建前自增与前自减运算符的副效应先序于其值计算(作为复合赋值定义所致的隐式规则)。
6) 内建逻辑与运算符 && 和内建逻辑或运算符 || 的第一(左)运算数的每个值计算和副效应,先序于第二(右)运算数的每个值计算和副效应。
7)条件运算符 ?: 中的第一表达式关联的每个值计算和副效应,都先序于与第二或第三表达式关联的每个值计算和副效应。
8) 内建赋值运算符和所有内建复合赋值运算符的副效应(修改左参数)后序于左右参数的值计算(但非副效应),并先序于赋值表达式的值计算(即先于返回被修改对象的引用)。
9) 内建逗号运算符 , 的第一(左)参数的每个值计算和副效应先序于第二(右)参数的每个值计算和副效应。
10) 列表初始化中,给定初始化器子句的每个值计算和副效应先序于与在初始化器的花括号逗号分隔列表中后随它的任何初始化器子句关联的值计算和副效应。
11) 既不先序于又不后序于另一函数调用的函数调用是非确定顺序的(程序必须表现为如同组成不同函数调用的 CPU 指令决不会交错,即使函数被内联)。

规则 11 有一个例外:在 std::execution::par_unseq 执行策略下执行的标准库算法所作的函数调用无顺序,并且可以任意交错。

(C++17 起)
12) 对分配函数( operator new )的调用相对于 new 表达式中构造函数参数的求值为非确定顺序 (C++17 前)先序 (C++17 起)
13) 从函数返回时,作为求值函数调用结果的临时量的复制初始化先序于return 语句的运算数结尾的所有临时量析构,也就是先序于环绕 return 语句的块的所有局部变量的析构。
(C++14 起)
14) 函数调用表达式中,指名函数的表达式先序于每个参数表达式和每个默认参数。
15) 函数调用表达式中,每个形参的初始化的值计算和副效应相对于任何其他形参的初始化的值计算和副效应为非确定顺序。
16) 用运算符记号调用时,每个重载的运算符遵循其所重载的内建运算符的定序规则。
17) 下标表达式 E1[E2] 中, E1 的每个值计算和副效应先序于 E2 的每个值计算和副效应。
18) 指向成员指针表达式 E1.*E2E1->*E2 中, E1 v的每个值计算和副效应都先序于 E2 的每个值计算和副效应(除非 E1 的动态类型不含 E2 所指的成员)。
19) 移位运算符表达式 E1<<E2E1>>E2 中, E1 的每个值计算和副效应先序于 E2 的每个值计算和副效应。
20) 每个简单赋值表达式 E1=E2 和每个复合赋值表达式 E1@=E2 中, E2 的每个值计算和副效应先序于 E1 的每个值计算和副效应。
21) 括号初始化器中表达式的逗号分隔列表中的每个表达式如同函数调用一般求值(非确定顺序)。
(C++17 起)

[编辑] 未定义行为

1) 若标量对象上的副效应相对同一标量对象上的另一副效应无顺序,则行为未定义

i = ++i + 2;       // C++11 前为未定义行为
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;      // 未定义行为

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

[编辑] 定义

求值可能产生副效应:即访问 volatile 左值所指代的对象、修改对象、调用库 I/O 函数或调用做任何这些动作的函数。

序列点 (sequence point) 是执行序列中的点,在该点所有来自序列中先前求值的副效应均已完成,而后继求值的副效应都未开始。

[编辑] 规则

1) 每个完整表达式结尾(典型地在分号)有一个序列点。

2) 调用函数时(无论该函数是否内联,无论是否使用函数调用语法),所有函数参数的求值(若存在)后有一个序列点,它在函数体内的任何表达式或语句执行前发生。

3) 复制函数返回值后,和函数外任何语句的执行前有一个序列点。

4) 一旦函数执行开始,则在被调用函数完成前,不求值来自调用方函数的表达式(函数不能交错)。

5) 每个使用内建(非重载)运算符的下列四种表达式的求值中,表达式 a 的求值后有一个序列点。

a && b
a || b
a ? b : c
a , b

[编辑] 未定义行为

1) 前后序列点间,至多可以修改标量对象的存储值一次,否则行为未定义

i = ++i + i++; // 未定义行为
i = i++ + 1; // 未定义行为( C++17 前)
i = ++i + 1; // 未定义行为( C++11 前)
++ ++i; // 未定义行为( C++11 前)
f(++i, ++i); // 未定义行为( C++17 前)
f(i = -1, i = -1); // 未定义行为( C++17 前)

2) 前后序列点间,访问表达式求值所修改的标量对象的先前值,必须只为确定要存储的值。若以任何其他方式访问,则行为未定义

cout << i << i++; // 未定义行为( C++17 前)
a[i] = i++; // 未定义行为( C++17 前)

[编辑] 缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

DR 应用于 出版时的行为 正确行为
CWG 1885 C++14 函数返回时自动变量的析构序列非显式 添加序列规则

[编辑] 引用

  • C++11 standard (ISO/IEC 14882:2011):
  • 1.9 Program execution [intro.execution]
  • 5.2.6 Increment and decrement [expr.post.incr]
  • 5.3.4 New [expr.new]
  • 5.14 Logical AND operator [expr.log.and]
  • 5.15 Logical OR operator [expr.log.or]
  • 5.16 Conditional operator [expr.cond]
  • 5.17 Assignment and compound assignment operators [expr.ass]
  • 5.18 Comma operator [expr.comma]
  • 8.5.4 List-initialization [dcl.init.list]

[编辑] 参阅

求值顺序C 文档