命名空間
變體
動作

求值順序

出自 cppreference.com
< 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)
儲存期指定符
初始化
 
 

任何表達式的任何部分之求值順序,包括函式引數的求值順序,皆是未指明的(除了下方列出的一些例外)。編譯器可以按任何順序對運算元和其他子表達式進行求值,且在再次對同一表達式求值時可能會選擇另一種順序。

在 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 起)

[編輯] 表達式的求值

每個表達式的求值包括:

  • 值計算 (Value computations):計算表達式所回傳的值。這可能涉及確定物件的身分(glvalue 求值,例如表達式回傳某個物件的引用時)或讀取先前分配給物件的值(prvalue 求值,例如表達式回傳數字或其他值時)。
  • 副作用 (Side effects) 的發起:對由 volatile glvalue 指定的物件進行存取(讀或寫)、修改(寫入)物件、呼叫函式庫 I/O 函式,或呼叫執行任何這些操作的函式。

[編輯] 排序

早於順序 (Sequenced before) 是同一執行緒內求值 AB 之間的一種非對稱、傳遞的二元關係。

  • 如果 A 早於順序 B(或者等效地,B晚於順序 (sequenced after) A),那麼 A 的求值將在 B 的求值開始之前完成。
  • 如果 A 不早於順序 BB 早於順序 A,那麼 B 的求值將在 A 的求值開始之前完成。
  • 如果 A 不早於順序 BB 不早於順序 A,則存在兩種可能性:
    • AB 的求值是無順序的 (unsequenced) :它們可以按任何順序執行,且可能重疊(在單個執行緒中,編譯器可能會交錯執行構成 AB 的 CPU 指令)。
    • AB 的求值是順序不定的 (indeterminately sequenced) :它們可以按任何順序執行,但不可重疊:要麼 AB 之前完成,要麼 BA 之前完成。下次求值同一表達式時,其順序可能會相反。

如果表達式 X 的每個值計算和每個副作用都早於表達式 Y 的每個值計算和每個副作用,則稱表達式 X 早於順序 Y

[編輯] 規則

1) 每個全表達式 (full-expression) 都早於順序下一個全表達式。
2) 任何運算子運算元的值計算(但非副作用)都早於順序該運算子結果的值計算(但非其副作用)。
3) 呼叫函式 func 時(不論函式是否為內聯,也不論是否使用顯式函式呼叫語法),下表中的每個項目都早於順序下一個項目:
  • 每個引數表達式以及指定 func 的後置表達式
(C++26 起)
  • func 函式體內的每個表達式或語句
(C++26 起)
4) 內建的後置遞增與後置遞減運算子的值計算早於順序其副作用。
5) 內建的前置遞增與前置遞減運算子的副作用早於順序其值計算(由於定義為複合賦值的隱含規則)。
6) 內建的邏輯與(AND)運算子 &&、內建的邏輯或(OR)運算子 || 以及內建的逗號運算子 , 的第一個(左側)運算元早於順序第二個(右側)運算元。
7) 條件運算子 ?: 的第一個運算元早於順序第二個或第三個運算元。
8) 內建的賦值運算子及所有內建複合賦值運算子的副作用(修改左側引數)晚於順序左右兩個引數的值計算(但非副作用),且早於順序賦值表達式的值計算(即在回傳已修改物件的引用之前)。
9)列表初始化 (list-initialization) 中,給定初始化子句的每個值計算和副作用都早於順序在括號括起來的逗號分隔初始化列表中的任何後續初始化子句之每個值計算和副作用。
10) 凡是未早於順序或晚於順序該函式之外的另一個表達式求值(可能是另一個函式呼叫)的函式呼叫,與該求值是順序不定的(程式的行為必須如同構成函式呼叫的 CPU 指令未與構成其他表達式求值的指令交錯,包括其他函式呼叫,即便該函式已被內聯)。
規則 10 有一個例外:在 std::execution::par_unseq 執行策略下執行的標準庫演算法所發起的函式呼叫是無順序的,且可能相互任意交錯。 (自 C++17 起)
11)new 表達式中,配置函式 (operator new) 的呼叫 與建構子引數的求值是順序不定的(至 C++17)早於順序建構子引數的求值(自 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 lvalue 指定的物件、修改物件、呼叫函式庫 I/O 函式,或呼叫執行任何這些操作的函式。

順序點 (sequence point) 是執行序列中的一個點,在該點之前序列中的所有求值副作用都已完成,且後續求值的副作用尚未開始。

[編輯] 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++ 標準。

DR 應用於 出版時的行為 正確的行為
CWG 1885 C++11 函式回傳時自動
變數的銷毀順序原先不明確
已新增排序規則
CWG 1949 C++11 C++ 標準中使用了「晚於順序」一詞但未定義 定義為
「早於順序」的逆向關係
CWG 1953 C++11 涉及記憶體位置的副作用和值計算
可能與該記憶體位置物件的啟動或結束
生命週期是無順序的
在這種情況下,行為是
未定義的
CWG 2146 C++98 涉及未定義行為的情況未考慮位元欄位 (bit-fields) 已納入考量

[編輯] 參考文獻

  • 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 語言文件 關於 求值順序
English Deutsch 日本語 中文(简体) 中文(繁體)