評価順序
提供: cppreference.com
あらゆる式のあらゆる部分の評価順序 (関数引数の評価順序も含みます) は未規定です (下に述べる例外があります)。 コンパイラは被演算子と他の部分式を任意の順序で評価してもよく、同じ式がもう一度評価されるときに別の順序を選んでも構いません。
C++ には左から右に評価するとか右から左に評価するといった概念はありません。 これを演算子の左結合や右結合と混同しないでください。 式 f1() + f2() + f3()
は、 + 演算子が左結合であるため、 (f1() + f2()) + f3()
と解析されますが、実行時には f3
の関数呼び出しが最初に評価されるかもしれませんし、最後かもしれませんし、 f1()
と f2()
の間に評価されるかもしれません。
Run this code
出力例:
b c a c a b
目次 |
[編集] 先行配列のルール (C++11以上)
[編集] 定義
[編集] 式の評価
それぞれの式の評価には以下の2つが含まれます。
- 値計算 ー その式によって返される値の計算。 これはオブジェクトの識別性の決定 (glvalue の評価、例えば式が何らかのオブジェクトへの参照を返す場合) や、オブジェクトに以前に代入された値の読み込み (prvalue の評価、例えば式が何らかの数値を返す場合) に影響することがあります。
- 副作用の開始 ー volatile 指定されたオブジェクトへのアクセス (読み書き)、オブジェクトへの変更 (書き込み)、ライブラリの入出力関数の呼び出し、またはそれらのいずれかの操作を行う関数の呼び出し。
[編集] 順序付け
「先行配列」は、非対称で推移的な、同じスレッド内の評価間の二項関係です。
- A が B に対して先行配列される場合、 A の評価は B の評価が始まる前に完了します。
- A が B に対して先行配列されず、 B が A に対して先行配列される場合、 B の評価は A の評価が始まる前に完了します。
- A が B に対して先行配列されず、 B が A に対して先行配列されない場合、2つの可能性があります。
- A の評価と B の評価が配列されない。 それらは任意の順序で行われる可能性があり、オーバーラップする可能性もあります (単一の実行のスレッド内で、コンパイラは A を構成する CPU 命令と B を構成する CPU 命令をインターリーブできます)。
- A の評価と B の評価が不定に配列される。 それらは任意の順序で行われる可能性がありますが、オーバーラップすることはありません。 A が B より前に完了するか、 B が A より前に完了するかの、いずれかです。 次回、同じ式が評価されるときに、順序が逆になる可能性はあります。
[編集] ルール
1) 完全式、すなわち
(C++20以上) |
(式の結果に適用される暗黙の変換、一時オブジェクトのデストラクタ呼び出し、デフォルトメンバ初期化子 (集成体を初期化するとき)、およびその他の、関数呼び出しを必要とするすべての言語構成要素を含む) のそれぞれの値計算および副作用は、次の完全式のそれぞれの値計算および副作用に対して先行配列されます。
2) 任意の演算子の被演算子の値計算 (副作用ではない) は、その演算子の結果の値計算 (副作用ではない) に対して先行配列されます。
3) 関数を呼ぶとき (その関数がインラインかどうかにかかわらず、また、明示的な関数呼び出し構文が使用されたかどうかにかかわらず)、あらゆる引数式および呼ばれる関数を指定する後置式に紐付くすべての値計算および副作用は、その呼ばれる関数の本体内のすべての式または文の実行に対して先行配列されます。
4) 組み込みの後置インクリメントおよび後置デクリメント演算子の値計算は、その副作用に対して先行配列されます。
5) 組み込みの前置インクリメントおよび前置デクリメント演算子の副作用は、その値計算に対して先行配列されます (複合代入としての定義による暗黙のルール)。
6) 組み込みの論理積演算子 && および組み込みの論理和演算子 || の1つめ (左) の引数のすべての値計算および副作用は、その2つめ (右) の引数のすべての値計算および副作用に対して先行配列されます。
8) 組み込みの代入演算子およびすべての組み込みの複合代入演算子の副作用 (左の引数の変更) は、その左と右の両方の引数の値計算 (副作用ではない) に対して後続配列され、代入式の値計算 (すなわち変更されたオブジェクトへの参照の返却) に対して先行配列されます。
10) リスト初期化において、ある初期化子節のすべての値計算および副作用は、その波括弧で囲まれたコンマ区切りの初期化子のリスト内でそれに後続する任意の初期化子節に紐付くすべての値計算および副作用に対して先行配列されます。
11) 別の関数呼び出しに対して先行配列も後続配列もされない関数呼び出しは、不定に配列されます (たとえ関数がインラインであっても、異なる関数呼び出しを構成する CPU 命令はインターリーブされないかのように動作します)。
ルール11にはひとつ例外があります。 std::execution::par_unseq 実行ポリシーの下で実行された標準ライブラリのアルゴリズムによって行われる関数呼び出しは、配列されず、任意にインターリーブされる可能性があります。 | (C++17以上) |
13) 関数から戻るとき、その関数呼び出しの評価の結果である一時オブジェクトのコピー初期化は、 return 文の被演算子の終わりで行われるすべての一時オブジェクトの破棄に対して先行配列されます。 また、その一時オブジェクトの破棄は、その return 文を囲むブロックのローカル変数の破棄に対して先行配列されます。
|
(C++14以上) |
14) 関数呼び出し式において、その関数を表す式は、すべての引数式およびすべてのデフォルト引数に対して先行配列されます。
15) 関数呼び出しにおいて、すべての仮引数の初期化の値計算および副作用は、他のあらゆる引数の値計算および副作用に対して不定に配列されます。
16) すべてのオーバーロードされた演算子は、演算子記法を用いて呼ばれるとき、そのオーバーロードしている組み込み演算子の配列ルールに従います。
17) 添字式
E1[E2] において、 E1 のすべての値計算および副作用は、 E2 のすべての値計算および副作用に対して先行配列されます。18) メンバポインタ式
E1.*E2 または E1->*E2 において、 E1 のすべての値計算および副作用は、 E2 のすべての値計算および副作用に対して先行配列されます (E1 の動的な型が E2 の参照先のメンバを含まない場合は除きます)。19) シフト演算子式
E1<<E2 および E1>>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 左辺値によって指定されるオブジェクトへのアクセス、オブジェクトの変更、ライブラリの入出力関数の呼び出し、またはそれらのいずれかの操作を行う関数の呼び出しなど)。
副作用完了点は、シーケンス内の先行する評価のすべての副作用が完了し、後続の評価の副作用が開始していない、実行シーケンス内の地点です。
[編集] ルール
1) それぞれの完全式の終わり (一般的にはセミコロンの位置) に副作用完了点があります。
2) 関数を呼ぶとき (その関数がインラインかどうかにかかわらず、また、関数呼び出し構文が使用されたかどうかにかかわらず)、すべての関数の実引数 (もしあれば) の評価の後、関数本体のいかなる式または文の実行よりも前に、副作用完了点があります。
3) 関数の戻り値のコピーの後、関数の外側のいかなる式の実行よりも前に、副作用完了点があります。
4) 関数の実行が開始されると、その呼ばれた関数の実行が完了するまで、呼び出し元の関数の式は評価されません (関数はインターリーブできません)。
5) 組み込みの (オーバーロードされていない) 演算子を使用した、以下の4つの式のそれぞれの評価において、式
a
の評価の後に副作用完了点があります。
a && b a || b a ? b : c a , b
[編集] 未定義動作
1) 前後の副作用完了点の間で、ひとつのスカラーオブジェクトは、多くとも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 | sequencing of the destruction of automatic variables on function return was not explicit | sequencing rules added |
[編集] 参考文献
- 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言語リファレンス
|