評価順序
関数引数の評価順序を含む、あらゆる式のあらゆる部分の評価順序は未規定 (unspecified) です(下記にリストされているいくつかの例外を除く)。コンパイラはオペランドや他の部分式を任意の順序で評価することができ、同じ式が再度評価される際には別の順序を選択する可能性があります。
C++には左から右、あるいは右から左への評価という概念はありません。これは演算子の左から右、右から左への結合規則と混同してはいけません。例えば、式 a() + b() + c() は、operator+ の左から右への結合規則により (a() + b()) + c() として解析されますが、実行時には c() が最初、最後、あるいは a() と b() の間に評価される可能性があります。
実行結果の例
b c a c a b
目次 |
[編集] 「先行順序付け」(Sequenced before) ルール (C++11以降)
[編集] 式の評価
各式の評価には以下が含まれます。
- 値計算 (Value computation): 式によって返される値の計算。これには、オブジェクトの同一性の決定(glvalue評価、例えば式があるオブジェクトへの参照を返す場合)や、以前にオブジェクトに代入された値の読み取り(prvalue評価、例えば式が数値やその他の値を返す場合)が含まれる場合があります。
- 副作用 (Side effects) の開始: volatileなglvalueで指定されたオブジェクトへのアクセス(読み書き)、オブジェクトの変更(書き込み)、ライブラリI/O関数の呼び出し、またはこれらの操作を行う関数の呼び出し。
[編集] 順序付け
先行順序付け (Sequenced before) とは、同じスレッド内の評価 A と B の間の非対称、推移的なペアワイズ関係です。
AがBに先行順序付けされている場合(あるいは、同等にBがAに後続順序付け (sequenced after) されている場合)、Aの評価はBの評価が始まる前に完了します。AがBに先行順序付けされておらず、かつBがAに先行順序付けされている場合、Bの評価はAの評価が始まる前に完了します。AがBに先行順序付けされておらず、かつBもAに先行順序付けされていない場合、2つの可能性があります。AとBの評価は順序付けられていない (unsequenced): これらは任意の順序で実行される可能性があり、オーバーラップする可能性があります(単一の実行スレッド内で、コンパイラはAとBを構成するCPU命令をインターリーブするかもしれません)。Aと B の評価は不定に順序付けられている (indeterminately sequenced): これらは任意の順序で実行される可能性がありますが、オーバーラップはしません。つまり、AがBの前に完了するか、BがAの前に完了するかのどちらかです。同じ式が次に評価されるときには、順序が逆になる可能性があります。
式 X に関連するすべての値計算とすべての副作用が、式 Y に関連するすべての値計算とすべての副作用の前に先行順序付けされている場合、式 X は式 Y に先行順序付けされていると言われます。
[編集] ルール
- すべての引数式と、func を指定する後置式
|
(C++26以降) |
- func の本体内のすべての式または文
|
(C++26以降) |
| ルール10には一つの例外があります: std::execution::par_unseq 実行ポリシーで実行される標準ライブラリアルゴリズムによる関数呼び出しは、順序付けられておらず、互いに任意にインターリーブされる可能性があります。 | (C++17以降) |
|
13) 関数呼び出し式では、関数を指名する式は、すべての引数式およびすべてのデフォルト引数に先行順序付けされます。
14) 関数呼び出しでは、すべてのパラメータの初期化の値計算と副作用は、他のパラメータの値計算と副作用に対して不定に順序付けられます。
15) すべてのオーバーロードされた演算子は、演算子記法で呼び出された場合、それがオーバーロードする組み込み演算子の順序付けルールに従います。
16) 添字式 E1[E2] では、E1 は E2 に先行順序付けされます。
17) メンバへのポインタ式 E1.*E2 または E1->*E2 では、E1 は E2 に先行順序付けされます(ただし、E1 の動的型が E2 が参照するメンバを含まない場合を除く)。
18) シフト演算子式 E1 << E2 と E1 >> E2 では、E1 は E2 に先行順序付けされます。
19) すべての単純代入式 E1 = E2 とすべての複合代入式 E1 @= E2 では、E2 は E1 に先行順序付けされます。
20) 丸括弧で囲まれた初期化子内のカンマ区切り式リストの各々の式は、関数呼び出しのように(不定に順序付けられて)評価されます。 |
(C++17以降) |
[編集] 未定義動作
以下の場合、動作は未定義です。
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
cout << i << i++; // undefined behavior until C++17 a[i] = i++; // undefined behavior until C++17 n = ++i + i; // undefined behavior
- 同じメモリ位置に対する副作用
- 同じメモリ位置にある任意のオブジェクトの値を使用する値計算
- そのメモリ位置と重複するストレージを占有するオブジェクトの生存期間の開始または終了
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以前のルール
a && b a || b a ? b : c a , b
[編集] C++11以前の未定義動作
以下の場合、動作は未定義です。
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
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言語ドキュメント の 評価順序
|