45

次のC++標準ISO/IEC 14882:2003(E)の引用(セクション5、パラグラフ4)を検討してください。

特に記載のない限り、個々の演算子のオペランドと個々の式の部分式の評価の順序、および副作用が発生する順序は指定されていません。53)前のシーケンスポイントと次のシーケンスポイントの間で、スカラーオブジェクトは、式の評価によって、格納されている値を最大で1回変更する必要があります。さらに、前の値は、保存される値を決定するためにのみアクセスされるものとします。この段落の要件は、完全な式の部分式の許容される順序ごとに満たされるものとします。それ以外の場合、動作は定義されていません。[例:

i = v[i++];  // the behavior is unspecified 
i = 7, i++, i++;  //  i becomes 9 

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

-例を終了]

i = ++i + 1の未定義の値を与えることに驚いたi。次のような場合に備えていないコンパイラの実装を知っている人はい2ますか?

int i = 0;
i = ++i + 1;
std::cout << i << std::endl;

operator=問題は、 2つの引数があるということです。最初のものは常にi参照です。この場合、評価の順序は重要ではありません。C++標準のタブー以外は問題ありません。

引数の順序が評価にとって重要であるような場合は考慮しないでください。たとえば、++i + iは明らかに未定義です。私の場合だけ考えてください i = ++i + 1

なぜC++標準はそのような式を禁止しているのですか?

4

15 に答える 15

62

operator=関数が始まる前に引数の副作用を完全に評価する必要がある2つの引数の関数として考えるのを間違えます。その場合、式i = ++i + 1には複数のシーケンスポイントがあり++i、割り当てが開始される前に完全に評価されます。しかし、そうではありません。ユーザー定義の演算子ではなく、組み込み代入演算子で評価されているもの。その式には1つのシーケンスポイントしかありません。

結果++i、割り当ての前(および加算演算子の前)に評価されますが、副作用がすぐに適用されるとは限りません。の結果++i + 1は常にと同じであるため、代入演算子の一部としてi + 2割り当てられる値です。iの結果++iは常にであるため、インクリメント演算子の一部としてi + 1割り当てられます。iどの値を最初に割り当てるかを制御するシーケンスポイントはありません。

このコードは、「前のシーケンスポイントと次のシーケンスポイントの間で、スカラーオブジェクトは、式の評価によって最大1回は保存された値を変更する」という規則に違反しているため、動作は定義されていません。ただし、実際i + 1には、またはi + 2が最初に割り当てられ、次に他の値が割り当てられ、最後にプログラムが通常どおり実行され続ける可能性があります。鼻の悪魔やトイレの爆発はなくi + 3、どちらもありません。

于 2009-12-07T15:30:01.033 に答える
37

iシーケンスポイントが介在しない2つの書き込みがあるため、これは未定義の動作であり、(単なる)未指定の動作ではありません。標準で指定されている限り、これは定義上この方法です。

この標準により、コンパイラーは、ストレージへの書き込みを遅らせるコードを生成するか、別の観点から、副作用を実装する命令を再シーケンスすることができます。これは、シーケンスポイントの要件に準拠している限り、選択した方法で行われます。

このステートメント式の問題は、iシーケンスポイントを介さずに2回の書き込みを意味することです。

i = i++ + 1;

1つの書き込みは、「プラス1」の元の値の値に対するものでiあり、もう1つは、その値「プラス1」に対するものです。これらの書き込みは、任意の順序で発生するか、標準で許可されている限り完全に爆発する可能性があります。理論的には、これにより、同時アクセスエラーをチェックする手間をかけずに、実装にライトバックを並行して実行する自由が与えられます。

于 2009-12-07T15:09:15.907 に答える
15

C / C ++は、シーケンスポイントと呼ばれる概念を定義します。これは、以前の評価のすべての効果がすでに実行されていることが保証される実行ポイントを指します。Sayingi = ++i + 1は、増分iし、それ自体に割り当てるため、未定義iです。どちらも、定義されたシーケンスポイントだけではありません。したがって、どちらが最初に発生するかは指定されていません。

于 2009-12-07T15:04:51.557 に答える
10

C ++ 11のアップデート(2011年9月30日)

やめて、これはC++11で明確に定義されています。C ++ 03でのみ未定義でしたが、C++11の方が柔軟性があります。

int i = 0;
i = ++i + 1;

この行の後にiは2になります。この変更の理由は...すでに実際に機能しており、C ++ 11のルールで定義したままにするよりも、未定義にする方が手間がかかるためです。実際、これが機能するようになったのは、意図的な変更というよりは偶然の問題なので、コードで実行しないでください!)。

馬の口から真っ直ぐに

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#637

于 2011-09-30T21:41:36.747 に答える
9

定義済みまたは未定義の2つの選択肢がある場合、どちらを選択しますか?

標準の作成者には、動作を定義するか、未定義として指定するかの2つの選択肢がありました。

そもそもそのようなコードを書くことの明らかに賢明でない性質を考えると、その結果を指定することは意味がありません。そのようなコードを思いとどまらせ、奨励したくないでしょう。それは何にも役に立たないか、必要ではありません。

さらに、標準化委員会には、コンパイラの作成者に何かを強制する方法がありません。特定の動作が必要だった場合、その要件は無視された可能性があります。

実用的な理由もありますが、上記の一般的な考え方に従属しているのではないかと思います。しかし、記録として、この種の式および関連する種類に必要な動作は、コードを生成する、共通部分式を除外する、レジスタとメモリ間でオブジェクトを移動するなどのコンパイラの機能を制限します。Cは、可視性が弱いためにすでに障害がありました。制限。Fortranのような言語は、エイリアス化されたパラメーターとグローバルが最適化のキラーであることに気づき、単にそれらを禁止したと私は信じています。

あなたが特定の表現に興味を持っていたことは知っていますが、与えられた構成の正確な性質はそれほど重要ではありません。複雑なコードジェネレーターが何をするかを予測するのは簡単ではなく、言語はばかげたケースでそれらの予測を必要としないようにします。

于 2009-12-07T15:09:04.873 に答える
8

標準の重要な部分は次のとおりです。

その保存された値は、式の評価によって最大で1回変更されます

値を2回変更します。1回は++演算子を使用し、もう1回は割り当てを使用します。

于 2009-12-07T15:08:28.960 に答える
7

標準のコピーは古く、例の1行目と3行目に既知の(および修正された)エラーが含まれていることに注意してください。以下を参照してください。

C ++標準コア言語の問題目次、リビジョン67、#351

Andrew Koenig:シーケンスポイントエラー:未指定または未定義?

このトピックは、標準を読むだけでは簡単ではありません(この場合はかなりあいまいです:()。

たとえば、それが適切に定義されているか、指定されていないか、または一般的な場合、実際にはステートメント構造だけでなく、実行時のメモリの内容(具体的には変数値)にも依存しますか?別の例:

++i, ++i; //ok

(++i, ++j) + (++i, ++j); //ub, see the first reference below (12.1 - 12.3)

ご覧ください(すべて明確で正確です):

JTC1 / SC22 /WG14N926「シーケンスポイント分析」

また、アンジェリカ・ランガーにはこのトピックに関する記事があります(前の記事ほど明確ではありませんが):

「C++でのシーケンスポイントと式の評価」

ロシア語での議論もありました(コメントと投稿自体に明らかに誤った記述がいくつかありますが):

"Точкиследования(シーケンスポイント)"

于 2009-12-07T16:10:57.363 に答える
4

「なぜ言語がこのように設計されているのか」と質問していると仮定します。

あなたはそれi = ++i + iが「明らかに未定義」であると言いますが、定義された値i = ++i + 1を残す必要がありますか?i率直に言って、それはあまり一貫していません。私は、すべてを完全に定義するか、すべてを一貫して指定しないことを好みます。C++では後者があります。それ自体はひどく悪い選択ではありません。1つには、同じ「ステートメント」で5つまたは6つの変更を行う邪悪なコードを書くことを防ぎます。

于 2009-12-07T15:15:34.083 に答える
4

次のコードは、間違った(予期しない)結果を得る方法を示しています。

int main()
{
  int i = 0;
  __asm { // here standard conformant implementation of i = ++i + 1
    mov eax, i;
    inc eax;
    mov ecx, 1;
    add ecx, eax;
    mov i, ecx;

    mov i, eax; // delayed write
  };
  cout << i << endl;
}

結果として1が出力されます。

于 2009-12-08T07:30:44.830 に答える
3

類推による議論:演算子を関数のタイプと考えるなら、それはある意味で理にかなっています。オーバーロードされたクラスがある場合operator=、代入ステートメントは次のようなものになります。

operator=(i, ++i+1)

this(最初のパラメーターは、実際にはポインターを介して暗黙的に渡されますが、これは単なる説明です。)

単純な関数呼び出しの場合、これは明らかに未定義です。最初の引数の値は、2番目の引数がいつ評価されるかによって異なります。ただし、プリミティブ型では、の元の値iが単純に上書きされるため、それを回避できます。その値は重要ではありません。しかし、あなたが自分operator=で他の魔法をやっていた場合、違いが表面化する可能性があります。

簡単に言えば、すべての演算子は関数のように動作するため、同じ概念に従って動作する必要があります。i + ++iが未定義の場合は、i = ++i同様に未定義にする必要があります。

于 2009-12-07T15:29:17.893 に答える
2

どうですか、私たちは皆、決して、決して、このようなコードを書かないことに同意しますか?コンパイラーがあなたが何をしたいのかわからない場合、あなたの後ろに続く貧しい樹液があなたが何をしたいのかを理解することをどのように期待しますか?i++を置く; それ自身のラインであなたを殺すことはありません

于 2009-12-07T15:46:18.360 に答える
1

i = v [i ++]; //動作は指定されてい
ませんi=++ i + 1; //動作は指定されていません

上記のすべての式は、未定義動作を呼び出します。

i = 7、i ++、i ++; //私は9になります

これで結構です。

スティーブサミットのC-FAQを読んでください。

于 2009-12-07T15:14:50.213 に答える
1

根本的な理由は、コンパイラが値の読み取りと書き込みを処理する方法にあります。コンパイラは、中間値をメモリに格納し、式の最後でのみ実際に値をコミットすることができます。式を「 1ずつ++i増やして返す」と読みますが、コンパイラは「の値をロードし、1を追加して返し、誰かが再び使用する前にメモリにコミットする」と見なす場合があります。コンパイラを使用することをお勧めします。プログラムの速度が低下するため、実際のメモリ位置への読み取り/書き込みをできるだけ回避します。ii

の特定のケースではi = ++i + 1、一貫した動作ルールの必要性が主な原因です。多くのコンパイラはそのような状況で「正しいこと」を行いますが、isの1つが実際にポインタであり、それを指している場合はiどうでしょうか。このルールがないと、コンパイラーは、ロードとストアーが正しい順序で実行されるように細心の注意を払う必要があります。このルールは、より多くの最適化の機会を可能にするのに役立ちます。

同様のケースは、いわゆる厳密なエイリアシングルールのケースです。いくつかの例外を除いてint、無関係なタイプ(たとえば、)の値を介して値(たとえば、)を割り当てることはできません。floatこれにより、コンパイラーは、float *使用されているものがの値を変更することを心配する必要がなくなりint、最適化の可能性が大幅に向上します。

于 2009-12-07T15:31:21.030 に答える
1

ここでの問題は、標準では、コンパイラが実行中にステートメントを完全に並べ替えることができることです。ただし、ステートメントを並べ替えることはできません(そのような並べ替えによってプログラムの動作が変更される場合に限ります)。したがって、式i = ++i + 1;は2つの方法で評価できます。

++i; // i = 2
i = i + 1;

また

i = i + 1;  // i = 2
++i;

また

i = i + 1;  ++i; //(Running in parallel using, say, an SSE instruction) i = 1

これは、ユーザー定義の型が混在している場合はさらに悪化します。++演算子は、型の作成者が望む型に影響を与える可能性があります。この場合、評価で使用される順序が非常に重要になります。

于 2009-12-07T15:37:03.393 に答える
0

から++i、私は「1」を割​​り当てる必要がありますがi = ++i + 1、を使用すると、値「2」を割り当てる必要があります。介在するシーケンスポイントがないため、コンパイラは同じ変数が2回書き込まれていないと想定できます。したがって、この2つの操作は任意の順序で実行できます。そうです、最終的な値が1の場合、コンパイラは正しいでしょう。

于 2009-12-07T15:56:16.653 に答える