1044

「シーケンスポイント」とは?

未定義の動作とシーケンス ポイントの関係は?

a[++i] = i;私は気分を良くするために、 のような面白くて複雑な表現をよく使います。なぜそれらの使用をやめなければならないのですか?

これを読んだ場合は、フォローアップの質問Undefined behavior and sequence points reloadedに必ずアクセスしてください。

(注: これはStack Overflow の C++ FAQへのエントリであることを意図しています。FAQ をこの形式で提供するという考えを批判したい場合は、すべての始まりとなった meta への投稿がそれを行う場所になります。回答への回答その質問は、FAQ のアイデアが最初に始まったC++ チャットルームで監視されているため、アイデアを思いついた人にあなたの回答が読まれる可能性が非常に高くなります。)
4

5 に答える 5

721

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

のアクセスのi1 つ ( の 1 つa[i]) は、最終的に i に格納される値 ( で発生) とは関係がないため、許可されi++ません。したがって、定義する良い方法はありません。コンパイラの - インクリメントされた値が保存される前または後にアクセスが行われるべきかどうか。したがって、動作は未定義です。

例 3 :

int x = i + i++ ;// Similar to above

C++11 のフォローアップ回答はこちら

于 2010-11-14T05:39:00.593 に答える
292

これは私の以前の回答のフォローアップであり、C++11関連の資料が含まれています。


前提条件:関係(数学)の初歩的な知識。


C ++ 11にシーケンスポイントがないというのは本当ですか?

はい!これは非常に真実です。

C ++ 11では、シーケンスポイントはSequenceedBeforeおよびSequenceedAfter(およびUnsequencedおよびIndeterminately Sequenced関係に置き換えられました。


この「前にシーケンスされた」ことは正確には何ですか?

前にシーケンス(§1.9/ 13)は次のような関係です:

単一のスレッドによって実行され、厳密な半順序を誘発する評価の間1

正式には、任意の2つの評価(以下を参照) Aが与えられ、がの前にシーケンスされBている場合Aは、の実行がの実行に先行することを意味します。が前にシーケンスされておらず、前にシーケンスされていない場合、およびはシーケンスれていません2 BA BABBAAB

評価ABは、前にシーケンスされた場合、または前にシーケンスされた場合に不確定にシーケンスされますが、どちらが3であるかは指定されていません。ABBA

[注]
1:厳密な半順序は、である集合に対する二項関係 です。つまり、すべての、、で、次のようになります"<"PasymmetrictransitiveabcP
。........(i)。a <bの場合¬(b <a)(asymmetry);
........(ii)。a<bおよびb<cの場合、a <c(transitivity)。
2:順序付けられていない評価の実行は重複する可能性があります。
3:不確定に順序付けられた評価は重複できませんが、どちらかが最初に実行される可能性があります。


C ++ 11の文脈での「評価」という言葉の意味は何ですか?

C ++ 11では、一般に式(または部分式)の評価には次のものが含まれます。

  • 値の計算(glvalue評価のためにオブジェクトのIDを決定し、prvalue評価ためにオブジェクトに以前に割り当てられた値をフェッチすることを含む)および

  • 副作用の開始。

今(§1.9/ 14)は言う:

完全式に関連するすべての値の計算と副作用は、評価される次の完全な式に関連するすべての値の計算と副作用のに順序付けられます。

  • 簡単な例:

    int x; x = 10; ++x;

    に関連する++x値の計算と副作用は、の値の計算と副作用の後にシーケンスされます。x = 10;


ですから、未定義動作と上記のものとの間には何らかの関係があるはずですよね?

はい!右。

(§1.9/ 15)では、

特に明記されていない限り、個々の演算子のオペランドと個々の式の部分式の評価は順序付けられていません4

例えば ​​:

int main()
{
     int num = 19 ;
     num = (num << 3) + (num >> 3);
} 
  1. 演算子のオペランドの評価は、+相互に関連して順序付けられていません。
  2. <<と演算子のオペランドの評価は、>>相互に関連して順序付けられていません。

4:プログラムの実行中に複数回評価される式では、その部分式のシーケンスされていない評価と不確定にシーケンスされた評価を、異なる評価で一貫して実行する必要はありません。

(§1.9/ 15)演算子のオペランドの値の計算は、演算子の結果の値の計算の前に順序付けられます。

つまり、x + yの値の計算では、の値の計算のxyにシーケンスが実行されます(x + y)

さらに重要なことには

(§1.9/ 15)スカラーオブジェクトの副作用がいずれかに対してシーケンスされていない場合

(a)同じスカラーオブジェクトに対する別の副作用

また

(b)同じスカラーオブジェクトの値を使用した値の計算。

動作は未定義です。

例:

int i = 5, v[10] = { };
void  f(int,  int);
  1. i = i++ * ++i; // Undefined Behaviour
  2. i = ++i + i++; // Undefined Behaviour
  3. i = ++i + ++i; // Undefined Behaviour
  4. i = v[i++]; // Undefined Behaviour
  5. i = v[++i]: // Well-defined Behavior
  6. i = i++ + 1; // Undefined Behaviour
  7. i = ++i + 1; // Well-defined Behaviour
  8. ++++i; // Well-defined Behaviour
  9. f(i = -1, i = -1); // Undefined Behaviour (see below)

関数を呼び出すとき(関数がインラインであるかどうかに関係なく)、引数式、または呼び出された関数を指定する後置式に関連するすべての値の計算と副作用は、本体のすべての式またはステートメントの実行前にシーケンスされます。関数と呼ばれます。[注: 異なる引数式に関連する値の計算と副作用は順序付けられていません。—エンドノート]

(5)(7)および(8)未定義の動作を呼び出さないでください。より詳細な説明については、以下の回答を確認してください。


最後の注意

投稿に欠陥を見つけた場合は、コメントを残してください。パワーユーザー(担当者> 20000)は、タイプミスやその他の間違いを修正するために、投稿を編集することを躊躇しないでください。

于 2010-11-15T11:12:25.303 に答える
11

変更には根本的な理由があると推測しています。古い解釈をより明確にするのは単なる表面的なものではありません。その理由は並行性です。詳細の指定されていない順序は、いくつかの可能な連続した順序付けの 1 つを選択するだけです。これは順序付けの前後とはまったく異なります。順序付けが指定されていない場合、同時評価が可能であるからです。古い規則ではそうではありません。たとえば、次のようになります。

f (a,b)

以前は、a の次に b、または b の次に a のいずれかでした。現在、a と b は、命令をインターリーブしたり、異なるコアで評価したりできます。

于 2010-12-07T19:44:51.267 に答える