名前空間
変種
操作

評価順序

From cppreference.com
< cpp‎ | language
 
 
C++言語
全般
フロー制御
条件実行文
if
繰り返し文 (ループ)
for
範囲for (C++11)
ジャンプ文
関数
関数宣言
ラムダ式
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)
キャスト
メモリ確保
クラス
クラス固有の関数プロパティ
explicit (C++11)
static

特殊メンバ関数
テンプレート
その他
 
 

関数引数の評価順序を含む、あらゆる式のあらゆる部分の評価順序は未規定 (unspecified) です(下記にリストされているいくつかの例外を除く)。コンパイラはオペランドや他の部分式を任意の順序で評価することができ、同じ式が再度評価される際には別の順序を選択する可能性があります。

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

目次

[編集] 「先行順序付け」(Sequenced before) ルール (C++11以降)

[編集] 式の評価

各式の評価には以下が含まれます。

  • 値計算 (Value computation): 式によって返される値の計算。これには、オブジェクトの同一性の決定(glvalue評価、例えば式があるオブジェクトへの参照を返す場合)や、以前にオブジェクトに代入された値の読み取り(prvalue評価、例えば式が数値やその他の値を返す場合)が含まれる場合があります。
  • 副作用 (Side effects) の開始: volatileなglvalueで指定されたオブジェクトへのアクセス(読み書き)、オブジェクトの変更(書き込み)、ライブラリI/O関数の呼び出し、またはこれらの操作を行う関数の呼び出し。

[編集] 順序付け

先行順序付け (Sequenced before) とは、同じスレッド内の評価 AB の間の非対称、推移的なペアワイズ関係です。

  • AB に先行順序付けされている場合(あるいは、同等に BA後続順序付け (sequenced after) されている場合)、A の評価は B の評価が始まる前に完了します。
  • AB に先行順序付けされておらず、かつ BA に先行順序付けされている場合、B の評価は A の評価が始まる前に完了します。
  • AB に先行順序付けされておらず、かつ BA に先行順序付けされていない場合、2つの可能性があります。
    • AB の評価は順序付けられていない (unsequenced): これらは任意の順序で実行される可能性があり、オーバーラップする可能性があります(単一の実行スレッド内で、コンパイラは AB を構成するCPU命令をインターリーブするかもしれません)。
    • A と B の評価は不定に順序付けられている (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演算子 ||、および組み込みのコンマ演算子 , の第1(左)オペランドは、第2(右)オペランドに先行順序付けされます。
7) 条件演算子 ?: の第1オペランドは、第2または第3オペランドに先行順序付けされます。
8) 組み込みの代入演算子およびすべての組み込みの複合代入演算子の副作用(左引数の変更)は、左右両方の引数の値計算(副作用ではない)の後続に順序付けされ、代入式の値計算(つまり、変更されたオブジェクトへの参照を返す前)の先行に順序付けされます。
9) リスト初期化において、特定の初期化子句のすべての値計算と副作用は、波括弧で囲まれたカンマ区切りの初期化子リスト内でそれに続くすべての初期化子句に関連するすべての値計算と副作用に先行順序付けされます。
10) 関数の外部の別の式評価(おそらく別の関数呼び出し)に対して先行順序付けも後続順序付けもされていない関数呼び出しは、その評価に対して不定に順序付けられます(プログラムは、関数呼び出しを構成するCPU命令が、たとえ関数がインライン化されていても、他の関数呼び出しを含む他の式の評価を構成する命令とインターリーブされないかのように振る舞う必要があります(as if))。
ルール10には一つの例外があります: std::execution::par_unseq 実行ポリシーで実行される標準ライブラリアルゴリズムによる関数呼び出しは、順序付けられておらず、互いに任意にインターリーブされる可能性があります。 (C++17以降)
11) メモリ確保関数の呼び出し(operator new)は、newにおけるコンストラクタ引数の評価に対して、不定に順序付けられ(C++17まで)先行順序付けられます(C++17以降)
12) 関数からリターンする際、関数呼び出しの評価結果である一時オブジェクトのコピー初期化は、returnのオペランドの最後でのすべての一時オブジェクトの破棄に先行順序付けされ、これはさらに、return 文を囲むブロックのローカル変数の破棄に先行順序付けされます。
13) 関数呼び出し式では、関数を指名する式は、すべての引数式およびすべてのデフォルト引数に先行順序付けされます。
14) 関数呼び出しでは、すべてのパラメータの初期化の値計算と副作用は、他のパラメータの値計算と副作用に対して不定に順序付けられます。
15) すべてのオーバーロードされた演算子は、演算子記法で呼び出された場合、それがオーバーロードする組み込み演算子の順序付けルールに従います。
16) 添字式 E1[E2] では、E1E2 に先行順序付けされます。
17) メンバへのポインタ式 E1.*E2 または E1->*E2 では、E1E2 に先行順序付けされます(ただし、E1 の動的型が E2 が参照するメンバを含まない場合を除く)。
18) シフト演算子式 E1 << E2E1 >> E2 では、E1E2 に先行順序付けされます。
19) すべての単純代入式 E1 = E2 とすべての複合代入式 E1 @= E2 では、E2E1 に先行順序付けされます。
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) 関数から戻るとき、関数呼び出しの結果のコピー初期化後に、そして returnexpression の終わりにあるすべての一時オブジェクトの破棄の前にシーケンスポイントがあります(もしあれば)。
4) 関数の返り値のコピー後、そして関数の外のいかなる式の実行前にもシーケンスポイントがあります。
5) 関数の実行が始まると、呼び出された関数の実行が完了するまで、呼び出し元の関数の式は評価されません(関数はインターリーブできません)。
6) 以下の4つの式のそれぞれを、組み込み(非オーバーロード)演算子を使用して評価する場合、式 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 「sequenced after」が使用されていたが、C++標準で定義されていなかった 「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 論理AND演算子 [expr.log.and]
  • 7.6.15 論理OR演算子 [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 standard (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 論理AND演算子 [expr.log.and]
  • 7.6.15 論理OR演算子 [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 standard (ISO/IEC 14882:2017)
  • 4.6 プログラム実行 [intro.execution]
  • 8.2.6 インクリメントとデクリメント [expr.post.incr]
  • 8.3.4 New [expr.new]
  • 8.14 論理AND演算子 [expr.log.and]
  • 8.15 論理OR演算子 [expr.log.or]
  • 8.16 条件演算子 [expr.cond]
  • 8.18 代入および複合代入演算子 [expr.ass]
  • 8.19 コンマ演算子 [expr.comma]
  • 11.6.4 リスト初期化 [dcl.init.list]
  • C++14 standard (ISO/IEC 14882:2014)
  • 1.9 プログラム実行 [intro.execution]
  • 5.2.6 インクリメントとデクリメント [expr.post.incr]
  • 5.3.4 New [expr.new]
  • 5.14 論理AND演算子 [expr.log.and]
  • 5.15 論理OR演算子 [expr.log.or]
  • 5.16 条件演算子 [expr.cond]
  • 5.17 代入および複合代入演算子 [expr.ass]
  • 5.18 コンマ演算子 [expr.comma]
  • 8.5.4 リスト初期化 [dcl.init.list]
  • C++11 standard (ISO/IEC 14882:2011)
  • 1.9 プログラム実行 [intro.execution]
  • 5.2.6 インクリメントとデクリメント [expr.post.incr]
  • 5.3.4 New [expr.new]
  • 5.14 論理AND演算子 [expr.log.and]
  • 5.15 論理OR演算子 [expr.log.or]
  • 5.16 条件演算子 [expr.cond]
  • 5.17 代入および複合代入演算子 [expr.ass]
  • 5.18 コンマ演算子 [expr.comma]
  • 8.5.4 リスト初期化 [dcl.init.list]

[編集] 関連項目

C言語ドキュメント評価順序
English 日本語 中文(简体) 中文(繁體)