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の作者が指定したシーケンスポイントの代替定義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++
。とのです。そのため、その場合の動作は未定義です(a
プリミティブ型と見なされる場合))。
関数呼び出し(関数がインラインかどうかに関係なく)、関数本体内の式またはステートメントの実行前に行われるすべての関数引数(存在する場合)の評価後(§1.9/17
)。
1:注:全式の評価には、語彙的に全式の一部ではない部分式の評価を含めることができます。たとえば、デフォルトの引数式(8.3.6)の評価に関与する部分式は、デフォルトの引数を定義する式ではなく、関数を呼び出す式で作成されたと見なされます。
2:示されている演算子は、5節で説明されている組み込み演算子です。これらの演算子の1つが有効なコンテキストでオーバーロードされている場合(第13節)、ユーザー定義の演算子関数を指定すると、式は関数の呼び出しを指定し、オペランドは引数リストを形成し、それらの間の暗黙のシーケンスポイントはありません。
未定義の動作とは何ですか?
標準セクションに未定義の動作を規定する§1.3.12
ように
この国際標準が要件を課さない、誤ったプログラム構造または誤ったデータの使用時に発生する可能性のある動作など3。
この国際標準が動作の明示的な定義の説明を省略した場合も、未定義の動作が予想されます。
3:予測できない結果を伴う状況の完全な無視から、(診断メッセージの発行の有無にかかわらず)環境に特徴的な文書化された方法での変換またはプログラムの実行中の動作、変換または実行の終了まで、許容される未定義の動作の範囲(診断メッセージの発行あり)。
つまり、未定義の動作は、あなたの鼻から飛び立つデーモンからあなたのガールフレンドが妊娠するまで何でも起こり得ることを意味します。
未定義の動作とシーケンスポイントの関係は何ですか?
その前に、未定義の動作、未指定の動作、実装定義の動作の違いを知っておく必要があります。
あなたもそれを知っている必要があり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つのシーケンスポイント間で変数を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)さらに、以前の値は、保存される値を決定するためにのみアクセスされるものとします。
どういう意味ですか?つまり、オブジェクトが完全な式内で書き込まれる場合、同じ式内でのオブジェクトへのすべてのアクセスは、書き込まれる値の計算に直接関与する必要があります。
たとえばi = i + 1
、i
(LHSおよびRHSの)すべてのアクセスは、書き込まれる値の計算に直接関与しています。結構です。
この規則は、アクセスが変更に明らかに先行するものに法的表現を効果的に制限します。
例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
(のアクセスa[i]
)の1つがiに格納されることになる値(これはi++
)そのため、定義する適切な方法がないため、コンパイラー-増分された値が格納される前または後にアクセスを行うかどうか。したがって、動作は未定義です。
例3:
int x = i + i++ ;// Similar to above
C ++ 11のフォローアップ回答はこちら。
*p++ = 4
未定義の動作ではありません。*p++
として解釈され*(p++)
ます。前のアドレスに格納されている(コピー)と値をp++
返しp
ます。なぜそれがUBを呼び出すのですか?まったく問題ありません。