未定義の動作とシーケンスポイント


987

「シーケンスポイント」とは何ですか?

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

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

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

(注:これは、Stack OverflowのC ++ FAQへのエントリになることを目的としています。このフォームでFAQを提供するという考えを批評したい場合は、これをすべて開始したメタへの投稿がそのための場所になります。回答その質問は、C ++チャットルームで監視されます。ここでは、FAQのアイデアが最初から始まっているため、アイデアを思いついた人があなたの答えを読む可能性が非常に高くなります。

回答:


683

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/182の評価後の以下の各式の評価

    • a && b (§5.14)
    • a || b (§5.15)
    • a ? b : c (§5.16)
    • a , b (§5.18)(ここで、a、bはコンマ演算子です。inはコンマ演算子でfunc(a,a++) ,はなく、単なる引数間の区切り文字です。aa++。とのです。そのため、その場合の動作は未定義です(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 + 1i(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のフォローアップ回答はこちら


45
*p++ = 4 未定義の動作ではありません。*p++として解釈され*(p++)ます。前のアドレスに格納されている(コピー)と値をp++返しpます。なぜそれがUBを呼び出すのですか?まったく問題ありません。
Prasoon Saurav、2010年

7
@マイク:AFAIK、リンクできるC ++標準の(合法的な)コピーはありません。
sbi 2010年

11
さて、あなたはISOの関連する注文ページへのリンクを持つことができます。とにかく、それについて考えると、「C ++標準の初歩的な知識」という語句は、標準を読んでいると初歩的なレベルを超えているため、用語では少し矛盾しているように見えます。たぶん、式の構文、操作の順序、演算子のオーバーロードなど、言語の基本的な理解が必要なものをリストできますか?
Mike DeSimone、2011年

41
私は確かに標準を引用すると、ティーチの初心者に最適な方法ではありませんよ

6
@Adrian最後の++iとへの割り当ての間にシーケンスポイントがないため、最初の式はUBを呼び出しますi。2番目の式は、式iがの値を変更しないため、UBを呼び出しませんi。2番目の例でi++,、代入演算子が呼び出される前に、シーケンスポイント()が続きます。
Kolyunya 2013

276

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


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


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

はい!これは本当です。

シーケンスポイントは、C ++ 11では、シーケンス前シーケンス後(およびシーケンスなしシーケンスなし)の関係に置き換えられました。


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

Sequenced Before (§1.9/ 13)は次の関係です。

単一のスレッドによって実行された評価の間で、厳密な半順序を引き起こします1

正式には、任意の2つの評価を与えられた意味(下記参照) AB、場合Aれる前に配列決定され B、その後の実行はA 先行するものの実行をB。もしA前に配列決定されていないBB前に配列決定されていないA場合、A及びBあるunsequenced 2

評価AとはBされている不定、配列決定のいずれかのときAの前に配列決定されBたりBする前に、配列決定されるAが、それは指定されていない3

[注]
1:厳密半順序である二項関係 "<"セット上Pでありasymmetric、そしてtransitive全てのために、すなわち、ab、及びcP:、我々は持っている
........(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. <<and >>演算子のオペランドの評価は、相互に順序付けられていません。

4:プログラムの実行中に複数回評価される式では、そのサブ式の順序付けられていない、および不規則に順序付けられた評価を、異なる評価で一貫して実行する必要はありません。

(§1.9/ 15)演算子のオペランドの値計算は、演算子の結果の値計算の前にシーケンスされます。

つまりx + yxおよびの値の計算では、の値の計算のy前に順序付けされ(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)は、タイプミスやその他の間違いを修正するために投稿を編集してください。


3
「非対称」の代わりに、「反対称」関係の前/後にシーケンスされます。これは、後で与えられる半順序の定義(Wikipediaにも同意します)に準拠するように、テキストで変更する必要があります。
TemplateRex

1
7)最後の例のアイテムがUBであるのはなぜですか?たぶんそうなのf(i = -1, i = 1)
ミハイル

1
「前に順序付け」関係の説明を修正しました。それは厳密半順序。当然のことながら、式はそれ自体の前に順序付けすることはできないため、関係は再帰的ではありません。したがって、それは非対称ではなく非対称です。
ThomasMcLeod 2014年

1
5)しっかりとした体調で私の心を吹き飛ばした。ヨハネスシャウブによる説明は、完全にわかりやすいものではありませんでした。特に私が++i+それを使用しているオペレーターの前に評価される値)、標準はまだその副作用を終える必要があるとは言いません。しかし、実際にlvaluei、それ自体がであるrefを返すため、評価を終了する必要があるため、副作用を終了している必要があります。したがって、値は最新でなければなりません。これは実際には非常にクレイジーな部分でした。
v.oddou 2015年

「ISO C ++委員会のメンバーは、シーケンスポイントの要素を理解するのは非常に難しいと考えていました。したがって、より明確な表現と正確さを高めるためだけに、上記の関係に置き換えることにしました。」-その主張のリファレンスはありますか?新しい関係は理解しにくいように思えます。
MM 2015年

30

C ++ 17N4659)には、式評価 のより厳密な順序を定義する、イディオマティックC ++の式評価順序を洗練する提案が含まれています。

特に、次の文

8.18代入および複合代入演算子
....

すべての場合において、割り当ては、右と左のオペランドの値計算の後、割り当て式の値計算の前にシーケンスされます。 右のオペランドは左のオペランドの前に順序付けられます。

以下の説明とともに

Xに関連付けられているすべての値の計算とすべての副作用が式Yに関連付けられているすべての値の計算とすべての副作用の前にシーケンス化される場合、式Xは式Yの前にシーケンス化されるといいます

問題のケースを含む、以前に未定義の動作のいくつかのケースを有効にします。

a[++i] = i;

ただし、他のいくつかの同様のケースでも未定義の動作が発生します。

N4140

i = i++ + 1; // the behavior is undefined

しかし N4659

i = i++ + 1; // the value of i is incremented
i = i++ + i; // the behavior is undefined

もちろん、C ++ 17準拠のコンパイラを使用しても、必ずしもそのような式の作成を開始する必要があるわけではありません。


なぜi = i++ + 1;c ++ 17で動作が定義されているのか、「右のオペランドが左のオペランドの前に順序付けられている」場合でも、「i ++」の変更と割り当ての副作用は順不同ですが、これらを解釈するために詳細を教えてください
ジャックX

@jackX私は答えを拡張しました:)。
AlexD

うん、「右のオペランドは左のオペランドの前に順番に並べられる」という文の解釈の詳細がより役立つと思います。たとえば、「右のオペランドは左のオペランドの前に順番に並べられます」などの意味は、右のオペランドに関連付けられた値の計算と副作用が左のオペランドの前にシーケンスされます。あなたがしたように:-)
ジャックX

11

変更には根本的な理由があると思います。古い解釈を明確にするのは単なる表面的なものではなく、その理由は同時実行性です。詳細な指定の順序は、いくつかの可能なシリアル順序の1つを選択するだけです。これは、順序の前後とはかなり異なります。指定された順序がない場合、同時評価が可能になるためです。古いルールではそうではありません。例:

f (a,b)

以前は、a、b、またはb、aのいずれかでした。これで、aとbは、インターリーブされた命令で、または異なるコアでさえも評価できます。


5
ただし、「a」または「b」のいずれかに関数呼び出しが含まれている場合、それらはシーケンスされていないのではなく、不確定にシーケンスされます。つまり、いずれかからのすべての副作用は、その他、ただし、コンパイラはどちらを先に実行するかについて一貫している必要はありません。それが当てはまらない場合、重複しない操作に依存する多くのコードが壊れます(たとえば、 'a'と 'b'がそれぞれ共有された静的状態を設定、使用、および削除する場合)。
スーパーキャット

2

ではC99(ISO/IEC 9899:TC3)、これまでこの議論から欠席思われる以下のsteteentsはevaluaitonの順序について作られています。

[...]部分式の評価の順序と副作用が発生する順序はどちらも指定されていません。(セクション6.5 pp 67)

オペランドの評価順序は指定されていません。代入演算子の結果を変更しようとしたり、次のシーケンスポイントの後でアクセスしたりした場合の動作は未定義です(セクション6.5.16 pp 91)。


2
質問にはCではなくC ++のタグが付けられています。これは、C ++ 17の動作が以前のバージョンの動作とかなり異なるため、C11、C99、C90などの動作とは関係がないため、問題ありません。それとの関係。全体として、これを削除することをお勧めします。さらに重要なのは、Cと同等のQ&Aを見つけて問題がないことを確認することです(特にC ++ 17はルールを変更することに注意してください。C++ 11以前の動作は多かれ少なかれ同じでした) C11で、C ++ 11以降がないのに対し、Cでそれを説明する言い回しは、まだ「配列ポイント」を使用かかわら。
ジョナサン・レフラー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.