c++ - 順序 - 論理演算がプログラムの動作に影響を与える(副作用をもつ)可能性のある式に対して実行されています




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

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

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

私はしばしばa[++i] = i;ような面白い畳み込みの表現を使用しa[++i] = i; 気分が良くなるように。 なぜ私はそれらを使用して停止する必要がありますか?

これを読んだら、フォローアップの質問未定義の動作とシーケンスポイントを再読み込みしてください。

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


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によって与えられたseq-pointsの交互定義:

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

C ++標準に列挙されている共通のシーケンスポイントは何ですか?

それらは:

  • 完全表現( §1.9/16 )の評価の終わりに(完全表現は別の表現の部分表現ではない表現である)

例:

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はカンマ演算子で、 func(a,a++) ,カンマ演算子ではなく、引数aa++間の単なるセパレータa++ 。 ( aがプリミティブ型でaと考えられる場合))
  • (機能がインラインであるか否かにかかわらず)、関数本体( §1.9/17 )内の式または文の実行前に行われるすべての関数引数(存在する場合)の評価後。

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

2:これらの演算子の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.

別の例をhere示しhere

今や§5/4規格には

  • 1) 前のシーケンスポイントと次のシーケンスポイントの間で、スカラオブジェクトは、最大でも1回の式の評価によってストアされた値を変更しなければならない。

どういう意味ですか?

非公式には、2つのシーケンスポイントの間で変数を複数回変更してはならないことを意味します。 式文では、 next sequence pointは通常セミコロンの終わりにあり、 previous sequence pointは前のステートメントの最後にあります。 式はまた、中間sequence points含むことができる。

上記の文から、次の式はUndefined Behaviorを呼び出します。

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

ii 1つ)のアクセスの1つがa[i]に格納される値とは何の関係もなく( i++で起こる)、定義する良い方法はないので、許可されません。増分された値が格納される前または後にアクセスが行われるべきかどうかを、私たちの理解またはコンパイラのために判断します。 したがって、その動作は未定義です。

例3:

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

C ++ 11のフォローアップはhere


C ++ 17N4659 )には、より厳密な式評価の順序を定義するIdiomatic C ++のための式評価規則の N4659提案が含まれています。

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

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準拠のコンパイラを使用しても、必ずしもそのような式の記述を開始する必要はありません。


ここまでの議論に欠けているようなC99(ISO/IEC 9899:TC3) 、評価の順序に関して次のような作業が行われている。

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

オペランドの評価の順序は不特定である。 代入演算子の結果を変更したり、次のシーケンスポイントの後にアクセスしようとすると、[sic]の動作は未定義です(6.5.16ページの91ページ)


私は変更の根本的な理由があると推測しています。古い解釈をより明瞭にすることは美観だけではありません。理由は並行性です。 指定された順序がない場合、並行した評価が可能である:古い規則ではそうではないので、順序付けの前後の順序とはかなり異なる。 たとえば:

f (a,b)

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







sequence-points