1. ホーム
  2. c++

[解決済み] 未定義の動作とシーケンスポイント

2022-03-19 22:12:34

質問

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

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

私はよく、以下のようなおかしな、複雑な表現を使います。 a[++i] = i; ということです。なぜ使うのをやめなければならないのでしょうか?

これを読んだ方は、ぜひフォローアップの質問をご覧ください。 未定義の動作とシーケンスポイントの再読み込み .

<サブ (注)これは、エントリーとして スタックオーバーフローのC++ FAQ . もし、このような形でFAQを提供することを批判したいのであれば すべての始まりとなったmetaへの投稿 は、そのための場所でしょう。その質問に対する回答は C++チャットルーム そのため、あなたの回答は、このアイデアを思いついた人たちに読まれる可能性が非常に高いのです)。

解決方法は?

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 はカンマ演算子。 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.

別の例 こちら .


では、Standard in §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 (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

のアクセスのうちの1つが許可されないためです。 i (の中の1つ)。 a[i] は、i に格納される値とは関係ありません(これは i++ そのため、私たちの理解でもコンパイラの理解でも、インクリメントされた値が格納される前にアクセスが行われるべきか後に行われるべきかを定義する良い方法がないのです。つまり、この挙動は未定義なのです。

例3.

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


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