web-dev-qa-db-ja.com

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

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

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

私はa[++i] = i;のように、面白くて複雑な表現をよく使います。なぜ私はそれらを使わないのですか?

これを読んだ場合は、フォローアップの質問を参照してください。未定義の動作とシーケンスポイントがリロードされました.

(注:これは Stack OverflowのC++ FAQ へのエントリであることを意味しています。この形のFAQ、それから これをすべて始めたmetaへの投稿 がそれを行う場所になるでしょう。質問は C++チャットルーム で監視されます。そこではFAQという考えが最初に出てきたので、あなたの答えはアイデアを思いついた人たちに読まれてください。)

952
Prasoon Saurav

C++ 98およびC++ 03

この回答は、C++標準の古いバージョン用です。 C++ 11およびC++ 14バージョンの標準には、正式には「シーケンスポイント」が含まれていません。代わりに、操作は「前にシーケンス化」、「非シーケンス化」または「不定シーケンス化」されます。正味の効果は基本的に同じですが、用語は異なります。


免責事項:はい。この答えは少し長いです。それを読んでいる間、忍耐を持ってください。すでにこれらのことを知っている場合、それらを再度読んでも気分が悪くなることはありません。

前提条件C++標準 の基礎知識


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

標準は言う

シーケンスポイントと呼ばれる実行シーケンスの特定のポイントで、以前の評価のすべての副作用が完了し、サイドなし後続の評価の効果が発生したものとする。 (§1.9/ 7)

副作用?副作用とは何ですか?

式の評価は何かを生成し、さらに実行環境の状態に変化がある場合、式(その評価)には何らかの副作用があると言われます。

例えば:

int x = y++; //where y is also an int

初期化操作に加えて、yname__の値は、++演算子の副作用のために変更されます。

ここまでは順調ですね。シーケンスポイントに進みます。 comp.lang.cの作成者Steve Summitによって指定されたseqポイントの代替定義:

シーケンスポイントは、ダストが落ち着いた時点であり、これまでに見られたすべての副作用が完全であることが保証されています。


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はコンマ演算子です。func(a,a++)では,はコンマ演算子ではなく、引数aname__とa++の間の単なるセパレータです。したがって、その場合の動作は未定義です(aname__がプリミティブ型と見なされる場合) )
  • 関数呼び出し時(関数がインラインであるかどうかにかかわらず)、関数本体(§1.9/17)内の式またはステートメントの実行前に行われるすべての関数引数(ある場合)の評価の後。

1:注:完全な式の評価には、字句的に完全な式の一部ではない部分式の評価を含めることができます。たとえば、デフォルト引数式(8.3.6)の評価に関係する部分式は、デフォルト引数を定義する式ではなく、関数を呼び出す式で作成されると見なされます

2:示されている演算子は、5項で説明されている組み込み演算子です。これらの演算子の1つが有効なコンテキストでオーバーロードされると(13節)、ユーザー定義の演算子関数を指定する場合、式は関数呼び出しを指定し、オペランドは引数リストを形成し、それらの間に暗黙のシーケンスポイントはありません。


未定義の動作とは何ですか?

標準では、セクション§1.3.12の未定義の動作を次のように定義しています。

この国際標準が課している誤ったプログラム構成または誤ったデータの使用時に発生する可能性があるような振る舞い要件なし 3

未定義の動作は、この国際規格が動作の明示的な定義の説明を省略した場合にも予想される場合があります。

3:予測できない結果を伴う状況を完全に無視することから、環境に特徴的な文書化された方法での診断またはプログラム実行中の動作(診断メッセージの発行の有無にかかわらず)、翻訳または実行の終了まで、許容される未定義の動作範囲(診断メッセージの発行)。

つまり、未定義の動作とは、anythingが鼻から飛び出すデーモンから妊娠中のガールフレンドへと発生する可能性があることを意味します。


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

それに入る前に、 未定義の動作、未指定の動作、実装定義の動作 の違いを知っておく必要があります。

また、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.

別の例 here


§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)さらに、保存する値を決定するためにのみ、以前の値にアクセスします。

どういう意味ですか?これは、オブジェクトが完全な式内で書き込まれる場合、同じ式内でそのオブジェクトへのすべてのアクセス書き込まれる値の計算に直接関与する必要があるを意味します。

たとえば、i = i + 1では、iname__のすべてのアクセス(L.H.SおよびR.H.Sで)は、書き込まれる値の計算に直接関与するです。結構です.

この規則は、正当な表現を、アクセスが明らかに修正に先行するものに制限します。

例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

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

例3:

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

C++ 11の回答をフォローしてください こちら

668
Prasoon Saurav

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


前提条件:関係(数学)の基礎知識。


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

はい!これは本当です。

Sequence PointsSequenced BeforeおよびSequenced After(およびUnsequencedに置き換えられましたおよびIndeterminately Sequencedrelations C++ 11。


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

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

単一の スレッド によって実行された評価とstrict半順序を誘導する評価の間1

正式には、任意の2つの評価が与えられたことを意味します(下記参照) AおよびBAsequenced beforeBの場合、Aの実行はBの実行の実行。 ABの前にシーケンスされておらず、BAの前にシーケンスされていない場合、AおよびBunsequencedです 2

評価AおよびBindeterminately sequencedであり、ABの前にシーケンスされるか、またはBAの前にシーケンスされるが、どちらが指定されていないか3

[ノート]
1:厳密な半順序は binary relation"<"であり、セットPasymmetric です。 transitive 、つまり、aのすべてのbc、およびPについては、次のようになります。
........(私)。 a <bの場合¬(b <a)(asymmetry);
........(ii)。 a <bおよびb <cの場合、a <c(transitivity)。
2:unsequenced evaluationsを実行すると、オーバーラップができます。
3:不定にシーケンスされた評価重複できませんが、どちらかを最初に実行できます。


C++ 11のコンテキストでの「評価」という言葉の意味は何ですか?

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

現在(§1.9/ 14)のコメント:

完全な式に関連付けられているすべての値の計算と副作用は、sequenced before前に評価されている次の完全な式に関連付けられているすべての値の計算と副作用

  • 簡単な例:

    int x;x = 10;++x;

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


だから、未定義の動作と上記のものとの間に何らかの関係がなければなりませんよね?

はい!そうです。

(§1.9/ 15)では、

特に断りのない限り、個々の演算子のオペランドと個々の式の部分式の評価はunsequencedです。4

例えば ​​:

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

4:プログラムの実行中に複数回評価される式では、unsequencedおよびindeterminately sequencedでその部分式の評価を一貫して実行する必要はありませんさまざまな評価。

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

つまり、x + yでは、xyの値の計算は、(x + y)の値の計算の前に順序付けられます。

さらに重要なことには

(§1.9/ 15)スカラーオブジェクトの副作用が次のいずれかに関連して順序付けられていない場合

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

または

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

動作はundefinedです。

例:

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を超える担当者)は、タイプミスやその他の間違いを修正するために投稿を編集することをheしないでください。

273
Prasoon Saurav

C++ 17N4659)には、aを定義する提案 慣用的C++の式評価順序の洗練 が含まれています。式評価の厳密な順序.

特に、次の文が追加されました。

8.18代入演算子と複合代入演算子
....

すべての場合において、代入は、左右のオペランドの値計算の後で、代入式の値計算の前に順序付けされます。 右のオペランドは左のオペランドの前にあります。

問題となっているものを含め、これまでに定義されていない動作のいくつかのケースが有効になります。

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準拠のコンパイラを使用しても、必ずしもそのような式を書き始める必要があるわけではありません。

24
AlexD

この変更には根本的な理由があると思います。古い解釈をより明確にすることは単に表面的なものではありません。その理由は並行性です。指定の順序が指定されていない場合、同時評価が可能であるため、これは前後の順序とはまったく異なります。古い規則ではそうではありません。例えば、

f (a,b)

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

11
Yttrill

これまでのところこの議論から欠けているように思われるC99(ISO/IEC 9899:TC3)では、評価の順序に関して次のような主張がなされています。

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

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

0
awiebe