f(i = -1、i = -1)が未定義の動作であるのはなぜですか?

269
Nicu Stiurca 2014-02-10 20:31.

私は評価違反の順序について読んでいました、そしてそれらは私を困惑させる例を与えます。

1)スカラーオブジェクトの副作用が、同じスカラーオブジェクトの別の副作用と比較して順序付けされていない場合、動作は定義されていません。

// snip
f(i = -1, i = -1); // undefined behavior

このコンテキストでiは、はスカラーオブジェクトであり、これは明らかに

算術型(3.9.1)、列挙型、ポインター型、メンバー型へのポインター(3.9.2)、std :: nullptr_t、およびこれらの型のcv修飾バージョン(3.9.3)は、まとめてスカラー型と呼ばれます。

その場合、ステートメントがどのように曖昧であるかはわかりません。関係なく、最初または2番目の引数が最初に評価された場合の、ように私には思えるiようになってしまう-1、との両方の引数もあります-1

誰かが明確にできますか?


更新

私は本当にすべての議論に感謝します。これまでのところ、@ harmicの回答は、一見簡単に見えますが、このステートメントを定義する際の落とし穴と複雑さを明らかにしているため、非常に気に入っています。@ acheong87は、参照を使用するときに発生するいくつかの問題を指摘していますが、これはこの質問の順序付けられていない副作用の側面と直交していると思います。


概要

この質問は非常に注目されたので、要点/回答を要約します。まず、「なぜ」は密接に関連しているが微妙に異なる意味、つまり「何のために」、「どのような理由で」、「どのような目的のために」を持ち得るのか、少し余談を述べさせてください。私は、彼らが「なぜ」のそれらの意味のどれに対処したかによって答えをグループ化します。

何のために

主な答えはここから来ているポール・ドレイパーと、マーティン・Jは、広範な答えとしてではないが類似の貢献します。ポール・ドレイパーの答えは要約すると

動作が定義されていないため、未定義の動作です。

答えは、C ++標準が何を言っているかを説明するという点で全体的に非常に良いです。また、f(++i, ++i);やなどのUBのいくつかの関連するケースについても説明しますf(i=1, i=-1);。関連する最初のケースでは、最初の引数をすべきかi+1、2番目の引数をすべきか、i+2またはその逆かは明確ではありません。2番目の例でiは、関数呼び出しの後で1にするか-1にするかが明確ではありません。これらのケースは両方とも、次のルールに該当するためUBです。

スカラーオブジェクトの副作用が、同じスカラーオブジェクトの別の副作用と比較して順序付けされていない場合、動作は定義されていません。

したがって、f(i=-1, i=-1)プログラマーの意図が(IMHO)明白で明白であるにもかかわらず、同じルールに該当するため、UBでもあります。

ポール・ドレイパーはまた、彼の結論の中で、

それは定義された振る舞いでしたか?はい。それは定義されましたか?番号。

これは、「どのような理由/目的がf(i=-1, i=-1)未定義の振る舞いとして残されたのか」という疑問に私たちを導きます。

どのような理由/目的のために

C ++標準にはいくつかの見落とし(おそらく不注意)がありますが、多くの省略は十分な理由があり、特定の目的を果たします。多くの場合、目的は「コンパイラー作成者の作業を容易にする」または「コードを高速化する」ことですが 、UBのままにする正当な理由があるかどうかを知ることに主に興味がありましたf(i=-1, i=-1)

有害スーパーキャットは、UBの理由を提供する主な答えを提供します。Harmicは、表面上はアトミックな代入操作を複数のマシン命令に分割する可能性のある最適化コンパイラーと、それらの命令をさらにインターリーブして最適な速度にする可能性があることを指摘しています。これはいくつかの非常に驚くべき結果につながる可能性があります:i彼のシナリオでは-2になります!したがって、harmicは、操作が順序付けられていない場合に、同じ値を変数に複数回割り当てる悪影響が生じる可能性があることを示しています。

supercatは、f(i=-1, i=-1)本来あるべきことを実行しようとする際の落とし穴についての関連する説明を提供します。彼は、一部のアーキテクチャでは、同じメモリアドレスへの複数の同時書き込みに対して厳しい制限があることを指摘しています。より些細なことを扱っていた場合、コンパイラはこれをキャッチするのに苦労する可能性がありf(i=-1, i=-1)ます。

davidfは、harmicの命令と非常によく似たインターリーブ命令の例も提供します。

有害な例、supercatの例、davidfの例はそれぞれ多少工夫されていますが、まとめると、f(i=-1, i=-1)未定義の振る舞いが必要であるという具体的な理由を提供するのに役立ちます。

ポール・ドレイパーの答えが「何のために」の部分をよりよく扱っていたとしても、それが理由のすべての意味に対処するのに最善の仕事をしたので、私はハーミックの答えを受け入れました。

その他の回答

JohnBは、(単なるスカラーではなく)オーバーロードされた代入演算子を検討すると、問題が発生する可能性があると指摘しています。

11 answers

346
harmic 2014-02-10 21:13.

操作は順序付けられていないため、割り当てを実行する命令をインターリーブできないことは言うまでもありません。CPUアーキテクチャによっては、そうすることが最適な場合があります。参照ページには次のように記載されています。

AがBの前にシーケンスされておらず、BがAの前にシーケンスされていない場合、2つの可能性があります。

  • AとBの評価は順序付けられていません。これらは任意の順序で実行でき、重複する可能性があります(単一の実行スレッド内で、コンパイラーはAとBを構成するCPU命令をインターリーブできます)

  • AとBの評価は不確定に順序付けられます。これらは任意の順序で実行できますが、重複することはできません。AがBの前に完了するか、BがAの前に完了するかのいずれかです。次に同じ式が実行されるときは、順序が逆になる場合があります。評価されます。

実行されている操作が値-1をメモリ位置に格納していると仮定すると、それ自体では問題が発生するようには見えません。しかし、コンパイラがそれを同じ効果を持つ別の命令セットに最適化できないことは言うまでもありませんが、操作が同じメモリ位置で別の操作とインターリーブされた場合は失敗する可能性があります。

たとえば、値-1インチをロードするよりも、メモリをゼロにしてからデクリメントする方が効率的であると想像してください。

f(i=-1, i=-1)

次のようになる可能性があります。

clear i
clear i
decr i
decr i

今私は-2です。

おそらく偽の例ですが、可能です。

209
Paul Draper 2014-02-10 20:39.

まず、「スカラーオブジェクトは、」のようなタイプを意味しintfloat(参照、またはポインタをC ++のスカラーオブジェクトとは何ですか?)。


第二に、それはより明白に見えるかもしれません

f(++i, ++i);

未定義の動作があります。だが

f(i = -1, i = -1);

あまり明白ではありません。

少し異なる例:

int i;
f(i = 1, i = -1);
std::cout << i << "\n";

「最後」、、、i = 1またはどのような割り当てが発生しましたかi = -1?規格では定義されていません。本当に、それはi可能性があります5(これがどのように当てはまるかについての完全にもっともらしい説明については、危害の答えを参照してください)。または、プログラムがセグメンテーション違反になる可能性があります。または、ハードドライブを再フォーマットします。

しかし今、あなたはこう尋ねます:「私の例はどうですか?私-1は両方の割り当てに同じ値()を使用しました。それについて何が不明確である可能性がありますか?」

あなたは正しいです... C ++標準委員会がこれを説明した方法を除いて。

スカラーオブジェクトの副作用が、同じスカラーオブジェクトの別の副作用と比較して順序付けされていない場合、動作は定義されていません。

彼らあなたの特別な場合のために特別な例外を作ることができたかもしれませんが、そうではありませんでした。(そして、なぜ彼らは?使用はそれが今までおそらく持っているでしょうか?なければならない)ので、iまだ可能性があり5。または、ハードドライブが空である可能性があります。したがって、あなたの質問に対する答えは次のとおりです。

動作が定義されていないため、未定義の動作です。

(多くのプログラマーが「未定義」は「ランダム」または「予測不可能」を意味すると考えているため、これは強調する価値があります。そうではありません。標準で定義されていないことを意味します。動作は100%一貫している可能性があり、それでも未定義です。)

それは定義された振る舞いでしたか?はい。それは定義されましたか?いいえ。したがって、「未定義」です。

とはいえ、「未定義」とは、コンパイラがハードドライブをフォーマットすることを意味するのではなく、それが可能であり、それでも標準に準拠したコンパイラであることを意味します。現実的には、g ++、Clang、およびMSVCはすべて期待どおりに機能すると確信しています。彼らは「しなければならない」だけではありません。


別の質問は、なぜC ++標準委員会がこの副作用をシーケンスなしにすることを選択したのかということかもしれません。その答えには、委員会の歴史と意見が含まれます。または、この副作用をC ++でシーケンスしないことの良い点は何ですか?、それが標準委員会の実際の推論であったかどうかにかかわらず、正当化を許可します。あなたはここで、またはprogrammers.stackexchange.comでそれらの質問をすることができます。

27
Ingo 2014-02-11 00:23.

2つの値が同じであるという理由だけでルールから例外を作らない実際的な理由:

// config.h
#define VALUEA  1

// defaults.h
#define VALUEB  1

// prog.cpp
f(i = VALUEA, i = VALUEB);

これが許可された場合を考えてみましょう。

今、数ヶ月後、変更する必要が生じます

 #define VALUEB 2

一見無害に見えますね。それでも突然、prog.cppはコンパイルされなくなりました。それでも、コンパイルはリテラルの値に依存すべきではないと私たちは感じています。

結論:コンパイルの成功は定数の値(タイプではなく)に依存するため、ルールに例外はありません。

編集

f(i = -1、i = -1)が未定義の動作であるのはなぜですか?A DIV BBが0の場合、フォームの定数式が一部の言語で許可されておらず、コンパイルが失敗することを指摘しました。したがって、定数を変更すると、他の場所でコンパイルエラーが発生する可能性があります。それは、私見、残念なことです。しかし、そのようなことをやむを得ないものに限定することは確かに良いことです。

12
davidf 2014-02-12 07:25.

混乱は、定数値をローカル変数に格納することは、Cが実行されるように設計されているすべてのアーキテクチャーで1つのアトミック命令ではないということです。この場合、コードが実行されるプロセッサは、コンパイラよりも重要です。たとえば、各命令が完全な32ビット定数を保持できないARMでは、変数にintを格納するには、複数の命令が必要です。一度に8ビットしか格納できず、32ビットレジスタで動作する必要があるこの擬似コードの例。iはint32です。

reg = 0xFF; // first instruction
reg |= 0xFF00; // second
reg |= 0xFF0000; // third
reg |= 0xFF000000; // fourth
i = reg; // last

コンパイラが最適化する場合、同じシーケンスを2回インターリーブする可能性があり、iに書き込まれる値がわからないことが想像できます。彼はあまり頭が良くないとしましょう。

reg = 0xFF;
reg |= 0xFF00;
reg |= 0xFF0000;
reg = 0xFF;
reg |= 0xFF000000;
i = reg; // writes 0xFF0000FF == -16776961
reg |= 0xFF00;
reg |= 0xFF0000;
reg |= 0xFF000000;
i = reg; // writes 0xFFFFFFFF == -1

しかし、私のテストでは、gccは、同じ値が2回使用され、1回生成され、何も奇妙なことをしないことを認識できるほど親切です。-1、-1を取得しますが、定数でさえ見た目ほど明白ではない可能性があることを考慮することが重要であるため、私の例は依然として有効です。

11
supercat 2014-02-11 06:55.

「役立つ」ことを試みていたコンパイラがまったく予期しない動作を引き起こす可能性があると考えられる理由がある場合、動作は一般に未定義として指定されます。

変数が複数回書き込まれ、書き込みが異なる時間に行われることを保証するものがない場合、ハードウェアの種類によっては、デュアルポートメモリを使用して異なるアドレスに対して複数の「ストア」操作を同時に実行できる場合があります。ただし、一部のデュアルポートメモリは、書き込まれた値が一致するかどうか関係なく、2つのストアが同時に同じアドレスにヒットするシナリオを明示的に禁止しています。このようなマシンのコンパイラは、同じ変数を2回連続して書き込もうとしていることに気付いた場合、コンパイルを拒否するか、2つの書き込みを同時にスケジュールできないようにする可能性があります。ただし、一方または両方のアクセスがポインターまたは参照を介している場合、コンパイラーは、両方の書き込みが同じ保管場所にヒットする可能性があるかどうかを常に判別できるとは限りません。その場合、書き込みを同時にスケジュールし、アクセス試行時にハードウェアトラップを引き起こす可能性があります。

もちろん、誰かがそのようなプラットフォームにCコンパイラを実装する可能性があるという事実は、アトミックに処理されるのに十分小さいタイプのストアを使用するときに、そのような動作をハードウェアプラットフォームで定義すべきではないことを示唆していません。2つの異なる値を順序付けられていない方法で格納しようとすると、コンパイラーがそれを認識していない場合、奇妙なことが発生する可能性があります。たとえば、次のようになります。

uint8_t v;  // Global

void hey(uint8_t *p)
{
  moo(v=5, (*p)=6);
  zoo(v);
  zoo(v);
}

コンパイラが「moo」の呼び出しをインライン化し、「v」を変更しないことがわかる場合は、5をvに格納し、次に6を* pに格納し、5を「zoo」に渡してからvの内容を「zoo」に渡します。「zoo」が「v」を変更しない場合、2つの呼び出しに異なる値を渡す方法はありませんが、とにかく簡単に発生する可能性があります。一方、両方のストアが同じ値を書き込む場合、そのような奇妙さは発生せず、ほとんどのプラットフォームでは、実装が奇妙なことをするための理にかなった理由はありません。残念ながら、一部のコンパイラ作成者は、「標準で許可されているため」以外のばかげた動作の言い訳を必要としないため、そのような場合でも安全ではありません。

9
Amadan 2014-02-10 20:42.

この場合、ほとんどの実装で結果が同じになるという事実は偶発的です。評価の順序はまだ定義されていません。考えてみてくださいf(i = -1, i = -2):ここでは、順序が重要です。あなたの例でそれが問題にならない唯一の理由は、両方の値がであるという偶然です-1

式が未定義の動作を持つものとして指定されている場合、悪意を持って準拠したコンパイラはf(i = -1, i = -1)、実行を評価して中止したときに不適切な画像を表示する可能性がありますが、それでも完全に正しいと見なされます。幸いなことに、私が知っているコンパイラはありません。

8
Martin J. 2014-02-10 20:52.

関数の引数式の順序付けに関する唯一のルールはここにあるように私には見えます:

3)関数を呼び出す場合(関数がインラインであるかどうか、および明示的な関数呼び出し構文が使用されているかどうか)、任意の引数式、または呼び出された関数を指定する後置式に関連するすべての値の計算と副作用は次のとおりです。呼び出された関数の本体にあるすべての式またはステートメントの実行前にシーケンスされます。

これは引数式間の順序付けを定義しないため、この場合は次のようになります。

1)スカラーオブジェクトの副作用が、同じスカラーオブジェクトの別の副作用と比較して順序付けされていない場合、動作は定義されていません。

実際には、ほとんどのコンパイラでは、引用した例は正常に実行されます(「ハードディスクの消去」やその他の理論上の未定義の動作の結果とは対照的です)。
ただし、割り当てられた2つの値が同じであっても、特定のコンパイラの動作に依存するため、これには責任があります。また、明らかに、異なる値を割り当てようとすると、結果は「本当に」未定義になります。

void f(int l, int r) {
    return l < -1;
}
auto b = f(i = -1, i = -2);
if (b) {
    formatDisk();
}
8
AlexD 2017-09-13 12:17.

C ++ 17は、より厳密な評価ルールを定義しています。特に、関数の引数をシーケンスします(ただし、順序は指定されていません)。

N5659 §4.6:15
評価AおよびBは、不定のいずれか場合に配列決定されているAが前に配列決定されるB又はBが前に配列決定されたAが、それは不特定です。[:不確定に順序付けられた評価は重複できませんが、どちらかを最初に実行できます。—エンドノート]

N5659 § 8.2.2:5
関連するすべての値の計算と副作用を含むパラメーターの初期化は、他のパラメーターの初期化に対して不確定に順序付けられます。

これにより、以前はUBであったいくつかのケースが可能になります。

f(i = -1, i = -1); // value of i is -1
f(i = -1, i = -2); // value of i is either -1 or -2, but not specified which one
5
JohnB 2014-02-11 04:08.

代入演算子がオーバーロードされる可能性があります。その場合、順序が重要になる可能性があります。

struct A {
    bool first;
    A () : first (false) {
    }
    const A & operator = (int i) {
        first = !first;
        return * this;
    }
};

void f (A a1, A a2) {
    // ...
}


// ...
A i;
f (i = -1, i = -1);   // the argument evaluated first has ax.first == true
2
Peng Zhang 2014-02-10 20:56.

これは、「intやfloatのようなもの以外に「スカラーオブジェクト」が何を意味するのかわからない」という答えにすぎません。

「スカラーオブジェクト」は「スカラー型オブジェクト」の略語、または単に「スカラー型変数」と解釈します。次いで、pointerenum(定数)は、スカラ型です。

これはスカラータイプのMSDN記事です。

2
polkovnikov.ph 2015-04-02 01:03.

実際には、コンパイラがi同じ値が割り当てられていることを2回チェックするという事実に依存しない理由があるため、単一の割り当てに置き換えることができます。表現があるとどうなりますか?

void g(int a, int b, int c, int n) {
    int i;
    // hey, compiler has to prove Fermat's theorem now!
    f(i = 1, i = (ipow(a, n) + ipow(b, n) == ipow(c, n)));
}

Related questions

MORE COOL STUFF

Reba McEntire は、彼女が息子の Shelby Blackstock と共有する「楽しい」クリスマスの伝統を明らかにしました:「私たちはたくさん笑います」

Reba McEntire は、彼女が息子の Shelby Blackstock と共有する「楽しい」クリスマスの伝統を明らかにしました:「私たちはたくさん笑います」

Reba McEntire が息子の Shelby Blackstock と共有しているクリスマスの伝統について学びましょう。

メーガン・マークルは、自然な髪のスタイリングをめぐってマライア・キャリーと結ばれました

メーガン・マークルは、自然な髪のスタイリングをめぐってマライア・キャリーと結ばれました

メーガン・マークルとマライア・キャリーが自然な髪の上でどのように結合したかについて、メーガンの「アーキタイプ」ポッドキャストのエピソードで学びましょう.

ハリー王子は家族との関係を修復できるという「希望を持っている」:「彼は父親と兄弟を愛している」

ハリー王子は家族との関係を修復できるという「希望を持っている」:「彼は父親と兄弟を愛している」

ハリー王子が家族、特にチャールズ王とウィリアム王子との関係について望んでいると主張したある情報源を発見してください。

ワイノナ・ジャッドは、パニックに陥った休暇の瞬間に、彼女がジャッド家の家長であることを認識しました

ワイノナ・ジャッドは、パニックに陥った休暇の瞬間に、彼女がジャッド家の家長であることを認識しました

ワイノナ・ジャッドが、母親のナオミ・ジャッドが亡くなってから初めての感謝祭のお祝いを主催しているときに、彼女が今では家長であることをどのように認識したかを学びましょう.

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セント ヘレナ島のジェイコブズ ラダーは 699 段の真っ直ぐ上る階段で、頂上に到達すると証明書が発行されるほどの難易度です。

The Secrets of Airline Travel Quiz

The Secrets of Airline Travel Quiz

Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!

バイオニック読書はあなたをより速く読むことができますか?

バイオニック読書はあなたをより速く読むことができますか?

BionicReadingアプリの人気が爆発的に高まっています。しかし、それは本当にあなたを速読術にすることができますか?

オーケーグッド770HPランボルギーニセンテナリオは十分に正気ではない

オーケーグッド770HPランボルギーニセンテナリオは十分に正気ではない

ランボルギーニの創設者であるフェルッチオランボルギーニが100歳になるのは毎日ではありません(そうです、彼は死んでいて、まだ死んでいると思います。

彼らが買った1台の車からAppleの車の計画について私たちが推測できること

彼らが買った1台の車からAppleの車の計画について私たちが推測できること

Appleが自動車分野に参入するという噂はかなり前から渦巻いており、AppleウォッチャーがSixtyEight Researchという会社がAppleの自動車研究開発のシェル会社である可能性が高いと判断したとき、その渦巻きは本当に渦巻いた。また、会社が購入した車は1台だけであることが知られており、その車はAppleが何を考えているかについての手がかりでいっぱいになる可能性があることも伝えています。

天文学者は太陽系の9番目の惑星の新しい証拠を見つけます

天文学者は太陽系の9番目の惑星の新しい証拠を見つけます

太陽系の外側にある架空の大きな物体である惑星Xの探索は、何十年にもわたって人間を魅了してきました。その検索の最新の章は、地球の10倍の大きさで、公転周期が15であるほど遠くにある惑星を指しています。

キャムニュートン、ゴッドダム

キャムニュートン、ゴッドダム

カムニュートンは昨日、簡単な265ヤードと3回のタッチダウンでファルコンズを引き裂き、別の素晴らしいゲームをしました。その日のハイライトは、上のタッチダウンスローでした。これは、視聴するたびにばかげているだけです。

米国のフィギュア スケートは、チーム イベントでの最終決定の欠如に「苛立ち」、公正な裁定を求める

米国のフィギュア スケートは、チーム イベントでの最終決定の欠如に「苛立ち」、公正な裁定を求める

ロシアのフィギュアスケーター、カミラ・バリエバが関与したドーピング事件が整理されているため、チームは2022年北京冬季オリンピックで獲得したメダルを待っています。

Amazonの買い物客は、わずか10ドルのシルクの枕カバーのおかげで、「甘やかされた赤ちゃんのように」眠れると言っています

Amazonの買い物客は、わずか10ドルのシルクの枕カバーのおかげで、「甘やかされた赤ちゃんのように」眠れると言っています

何千人ものAmazonの買い物客がMulberry Silk Pillowcaseを推奨しており、現在販売中. シルクの枕カバーにはいくつかの色があり、髪を柔らかく肌を透明に保ちます。Amazonで最大46%オフになっている間にシルクの枕カバーを購入してください

パデュー大学の教授が覚醒剤を扱った疑いで逮捕され、女性に性的好意を抱かせる

パデュー大学の教授が覚醒剤を扱った疑いで逮捕され、女性に性的好意を抱かせる

ラファイエット警察署は、「不審な男性が女性に近づいた」という複数の苦情を受けて、12 月にパデュー大学の教授の捜査を開始しました。

コンセプト ドリフト: AI にとって世界の変化は速すぎる

コンセプト ドリフト: AI にとって世界の変化は速すぎる

私たちの周りの世界と同じように、言語は常に変化しています。以前の時代では、言語の変化は数年または数十年にわたって発生していましたが、現在では数日または数時間で変化する可能性があります。

SF攻撃で91歳のアジア人女性が殴られ、コンクリートに叩きつけられた

犯罪擁護派のオークランドが暴力犯罪者のロミオ・ロレンゾ・パーハムを釈放

SF攻撃で91歳のアジア人女性が殴られ、コンクリートに叩きつけられた

認知症を患っている 91 歳のアジア人女性が最近、47 番街のアウター サンセット地区でロメオ ロレンゾ パーハムに襲われました。伝えられるところによると、被害者はサンフランシスコの通りを歩いていたところ、容疑者に近づき、攻撃を受け、暴行を受けました。

ℝ

“And a river went out of Eden to water the garden, and from thence it was parted and became into four heads” Genesis 2:10. ? The heart is located in the middle of the thoracic cavity, pointing eastward.

メリック・ガーランドはアメリカに失敗しましたか?

バイデン大統領の任期の半分以上です。メリック・ガーランドは何を待っていますか?

メリック・ガーランドはアメリカに失敗しましたか?

人々にチャンスを与えることは、人生で少し遅すぎると私は信じています。寛大に。

Language