19

C ++ 11標準のn3290ドラフト(実際の標準テキストにできるだけ近い)を読んでいますが、i = i++ + 1;未定義の動作が発生することに気づきました。私は以前に同様の質問を見たことがありますが、それらは古い基準(シーケンスポイント)の観点から回答されました。新しい標準では、代わりに、式と部分式の実行の間の関係の前後の順序付けの概念が導入されています。

1.9 13前にシーケンスされたのは、単一のスレッド(1.10)によって実行される評価間の非対称で推移的なペアワイズ関係であり、これらの評価の間に半順序が生じます。AとBの2つの評価が与えられた場合、AがBの前にシーケンスされている場合、Aの実行はBの実行に先行します。AがBの前にシーケンスされておらず、BがAの前にシーケンスされていない場合、AとBはシーケンスされていません。[注:順序付けられていない評価の実行は重複する可能性があります。—end note]評価AとBは、AがBの前にシーケンスされるか、BがAの前にシーケンスされる場合、不確定にシーケンスされますが、どちらが指定されていません。[注:不確定に順序付けられた評価は重複できませんが、どちらかが最初に実行される可能性があります。—エンドノート]

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

1.9 15特に明記されていない限り、個々の演算子のオペランドおよび個々の式の部分式の評価は順序付けられていません。[注:プログラムの実行中に複数回評価される式では、その部分式のシーケンスされていない評価とシーケンスが不確定な評価を、異なる評価で一貫して実行する必要はありません。—end note]演算子のオペランドの値の計算は、演算子の結果の値の計算の前に順序付けられます。スカラーオブジェクトの副作用が、同じスカラーオブジェクトの別の副作用、または同じスカラーオブジェクトの値を使用した値の計算に比べて順序付けされていない場合、動作は定義されていません。

[ Example:
void f(int, int);
void g(int i, int* v) {
i = v[i++]; // the behavior is undefined
i = 7, i++, i++; // i becomes 9
i = i++ + 1; // the behavior is undefined
i = i + 1; // the value of i is incremented
f(i = -1, i = -1); // the behavior is undefined
}
—end example ]

私がそれを理解する方法では、それは次のように機能します:

  • operator=2つのオペランド式があります。iとを参照しi++ + 1ます。どちらも互いにシーケンスされていません。2つ目はに副作用がありますiが、1つ目は副作用がないか、値の計算に使用されていないようです(または、「同じスカラーオブジェクトの値を使用した値の計算」を参照していますか?実際には依存していますか? iに格納されている値?そうは思わない)、したがって、未定義の動作ではありません。
  • operator=実行は、両方のオペランド評価の後にシーケンスされます。に副作用がiありますが、両方のオペランドを参照して適切に順序付けられているため、未定義の動作ではありません。
  • i++ + 1明らかに定義された動作です。

私はここで何かについて間違っていますか?それとも、この行は他の理由で未定義の動作ですか?

PS。標準は実際に言う

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

、そしてそれはこの文脈での副作用についてはまったく言及していません。ただし、シーケンス関係は、式の評価と、評価=値の計算+副作用の間でのみ定義されます。したがって、このドラフトはここでは矛盾していると想定するか、この行では値の計算ではなく評価を意味していると想定する必要があります。それとも私はここで間違っていますか?

編集:

私はここで自分自身に答えると思いますが、それが私の混乱の理由でした:

5 1式は、計算を指定する一連の演算子とオペランドです。式は値になり、副作用を引き起こす可能性があります。

したがって、演算子のオペランド自体は部分式ではありません。したがって、全体の値の計算のみi = i++ + 1;がシーケンスされ、副作用のシーケンスについては標準で言及されていません。それが未定義の理由です。

たとえば、operator=与えられたタイプに対してオーバーロードされました(したがって、暗黙の関数呼び出しになります)、それは未定義の動作ではありませんよね?

4

3 に答える 3

7

これは「未定義」ではなく「未定義の動作」です。未定義とは、空のプログラムの出力、ランダムな終了、爆発など、マシンが何でもできることを意味します。もちろん、別のプラットフォームに移植するときの微妙に予想外の値は、より可能性の高い結果です。

未定義の動作は、2 つの副作用が互いに相対的に順序付けされずに同じスカラーに適用される場合に適用されます。この場合、副作用はたまたま同じですが (どちらもi式の前の元の値から増加します)、標準の文字により、結合して UB が生成されます。

,?:||、および を除いて&&、演算子は C++11 §5.15/2 などの用語で順序付けルールを定義しないため、副作用は順序付けされていません。

2 番目の式が評価される場合、最初の式に関連付けられたすべての値の計算と副作用は、2 番目の式に関連付けられたすべての値の計算と副作用の前に並べられます。

代入演算子は、§5.17/1 という特別な順序付け規則を定義します。

いずれの場合も、代入は、右オペランドと左オペランドの値の計算の後、代入式の値の計算の前に順序付けされます。

i = i ++ + 1の副作用はi ++値計算の一部ではないため、これは役に立ちません。

于 2012-05-28T05:30:56.603 に答える
4

C++03 では、i = ++i + 1;との両方i = i++ + 1 が明確に定義されていません。

しかし、C++11 ではi = ++i + 1が明確に定義されます。しかしi=i++ + 1、まだそうではありません。

詳細については、このようにコンテンツを参照してください シーケンス ルールと例の不一致

于 2012-05-28T02:12:00.353 に答える
3

値の計算と副作用の解決を混同しています。の値は割り当ての前に計算する必要がありますが、完全な式の完了以外に、割り当てに関するi++副作用(への変更)をシーケンスするものはありません。i

対照的な例として、コンマ演算子を見てください。「左の式に関連付けられたすべての値の計算と副作用は、右の式に関連付けられたすべての値の計算と副作用の前に順序付けられます。」値の計算と副作用が別々に言及されていることに注意してください。そのような割り当ての規則はありません。

于 2012-05-28T02:05:45.007 に答える