C++98 および C++03
この回答は、C++ 標準の古いバージョンに対するものです。標準の C++11 および C++14 バージョンには、正式には「シーケンス ポイント」が含まれていません。代わりに、操作は「前に順序付けられる」または「順序付けられない」または「不確定に順序付けられる」。正味の効果は本質的に同じですが、用語が異なります。
免責事項:わかりました。この答えは少し長いです。だから、それを読む間、忍耐を持ってください。これらのことをすでに知っている場合は、もう一度読んでも気が狂うことはありません。
前提条件: C++ 標準の初歩的な知識
シーケンスポイントとは?
スタンダードは言う
シーケンス ポイントと呼ばれる実行シーケンスの特定のポイントでは、前の評価のすべての副作用が完了し、後続の評価の副作用は発生しません。(§1.9/7)
副作用?副作用とは何ですか?
式の評価は何かを生成し、さらに実行環境の状態に変化がある場合、式 (その評価) には何らかの副作用があると言われます。
例えば:
int x = y++; //where y is also an int
初期化操作に加えて、演算子y
の副作用により、の値が変更されます。++
ここまでは順調ですね。シーケンスポイントに移ります。comp.lang.c の作成者によって与えられた seq-points の代替定義Steve Summit
:
シーケンス ポイントは、ほこりが落ち着いた時点で、これまでに見られたすべての副作用が完全であることが保証されています。
C++ 標準にリストされている一般的なシーケンス ポイントは何ですか?
それらは:
完全な式 ( ) の評価の最後§1.9/16
(完全な式は、別の式の部分式ではない式です。) 1
例 :
int a = 5; // ; is a sequence point here
最初の式の評価後、次の各式の評価で ( §1.9/18
) 2
a && b (§5.14)
a || b (§5.15)
a ? b : c (§5.16)
a , b (§5.18)
(ここで、 a , b はコンマ演算子です。 inはコンマ演算子でfunc(a,a++)
,
はなく、単に引数a
との間の区切り記号a++
です。したがって、その場合の動作は未定義です ( ifa
はプリミティブ型と見なされます))。
関数呼び出し時 (関数がインラインであるかどうかにかかわらず)、関数本体内の式またはステートメントの実行前に行われるすべての関数引数 (存在する場合) の評価後 ( §1.9/17
)。
1 : 注 : 完全式の評価には、字句的に完全式の一部ではない部分式の評価を含めることができます。たとえば、デフォルト引数式 (8.3.6) の評価に含まれる部分式は、デフォルト引数を定義する式ではなく、関数を呼び出す式で作成されたと見なされます。
2 : 5 節で説明されているように、示されている演算子は組み込み演算子です。これらの演算子の 1 つが有効なコンテキストでオーバーロードされている場合 (13 節)、ユーザー定義の演算子関数を指定すると、式は関数呼び出しを指定し、オペランドは引数リストを形成し、それらの間に暗黙のシーケンス ポイントはありません。
未定義の動作とは?
規格では、セクションの未定義の動作を次のように定義し§1.3.12
ています。
誤ったプログラム構造または誤ったデータを使用したときに発生する可能性のある動作など、この国際規格が要件を課していない3。
この国際規格が動作の明示的な定義の記述を省略した場合も、未定義の動作が予想される場合があります。
3 : 許容される未定義の動作は、状況を完全に無視して予測不可能な結果をもたらすことから、変換またはプログラムの実行中に環境に特有の文書化された方法で動作すること (診断メッセージの発行の有無にかかわらず)、変換または実行の終了にまで及びます。 (診断メッセージの発行を伴う)。
つまり、未定義の動作とは、悪魔が鼻から飛び出すことから、ガールフレンドが妊娠することまで、あらゆることが起こり得ることを意味します。
未定義の動作とシーケンス ポイントの関係は?
その前に、 Undefined Behaviour 、 Unspecified Behavior 、および Implementation Defined Behaviorの違いを知っておく必要があります。
あなたもそれを知っている必要がありthe order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified
ます。
例えば:
int x = 5, y = 6;
int z = x++ + y++; //it is unspecified whether x++ or y++ will be evaluated first.
ここに別の例があります。
現在、標準は次のように§5/4
述べています
- 1)前のシーケンス ポイントと次のシーケンス ポイントの間で、スカラー オブジェクトの格納値は、式の評価によって最大 1 回変更されます。
どういう意味ですか?
非公式には、2 つのシーケンス ポイント間で変数を複数回変更してはならないことを意味します。式ステートメントでは、next sequence point
は通常終了セミコロンにあり、previous sequence point
は前のステートメントの最後にあります。式には中間体も含まれる場合がありますsequence points
。
上記の文から、次の式は未定義の動作を呼び出します。
i++ * ++i; // UB, i is modified more than once btw two SPs
i = ++i; // UB, same as above
++i = 2; // UB, same as above
i = ++i + 1; // UB, same as above
++++++i; // UB, parsed as (++(++(++i)))
i = (i, ++i, ++i); // UB, there's no SP between `++i` (right most) and assignment to `i` (`i` is modified more than once btw two SPs)
ただし、次の表現は問題ありません。
i = (i, ++i, 1) + 1; // well defined (AFAIK)
i = (++i, i++, i); // well defined
int j = i;
j = (++i, i++, j*i); // well defined
- 2)さらに、保存する値を決定するためにのみ、以前の値にアクセスする必要があります。
どういう意味ですか?これは、オブジェクトが完全な式内で書き込まれる場合、同じ式内でのオブジェクトへのすべてのアクセスは、書き込まれる値の計算に直接関与する必要があることを意味します。
たとえば、 (LHS と RHS の)i = i + 1
のすべてのアクセスは、書き込まれる値の計算に直接関与します。それで大丈夫です。i
このルールは、正当な表現を、アクセスが明らかに変更に先行する表現に効果的に制限します。
例 1:
std::printf("%d %d", i,++i); // invokes Undefined Behaviour because of Rule no 2
例 2:
a[i] = i++ // or a[++i] = i or a[i++] = ++i etc
のアクセスのi
1 つ ( の 1 つa[i]
) は、最終的に i に格納される値 ( で発生) とは関係がないため、許可されi++
ません。したがって、定義する良い方法はありません。コンパイラの - インクリメントされた値が保存される前または後にアクセスが行われるべきかどうか。したがって、動作は未定義です。
例 3 :
int x = i + i++ ;// Similar to above
C++11 のフォローアップ回答はこちら。