d == 0の場合、 'd / = d'がゼロ除算例外をスローしないのはなぜですか?


81

ゼロ除算の例外が発生しない理由がよくわかりません。

int d = 0;
d /= d;

ゼロ除算の例外を取得することを期待していましたが、代わりにd == 1

d /= dゼロ除算の例外をスローしないのはなぜd == 0ですか?


25
それは未定義の振る舞いです。
LF

51
ゼロ除算のようなものはありません。
πάνταῥεῖ

15
コメントの一部を明確にするために:「ゼロ除算例外」に関するメッセージが表示された場合、それはオペレーティングシステムが問題が発生したことを通知します。これはC ++の例外ではありません。C ++では、例外はthrowステートメントによってスローされます。他には何もありません(未定義の振る舞いの土地にいる場合を除く)。
ピートベッカー

9
C ++には「ゼロ除算例外」のようなものはありません。
アルギルダスPreidžius

6
@ user11659763 「それが未定義の動作である理由です:それは完全にターゲットに依存します。」-それは未定義の振る舞いが意味することはありません; あなたが説明しているのは、実装で定義された動作です。未定義の振る舞いは、はるかに強力なステートメントです。
marcelm

回答:


108

C ++には、キャッチする「ゼロ除算」例外はありません。観察している動作は、コンパイラの最適化の結果です。

  1. コンパイラは、未定義の振る舞いが発生しないことを前提としています
  2. C ++でのゼロ除算は未定義の動作です
  3. したがって、ゼロ除算引き起こす可能性のあるコードはそうしないと推定されます。
    • そして、ゼロ除算を引き起こさなければならないコードは決して起こらないと推定されます
  4. したがって、コンパイラーは、未定義の振る舞いが発生しないため、このコード(d == 0)の未定義の振る舞いの条件が発生してはならないと推測します。
  5. したがって、d / d常に1に等しくなければなりません。

しかしながら...

コードを少し調整するだけで、コンパイラにゼロによる「実際の」除算をトリガーさせることができます。

volatile int d = 0;
d /= d; //What happens?

だから今、疑問が残ります:基本的にコンパイラにこれが起こることを許可するように強制したので、何が起こりますか?これは未定義の動作ですが、コンパイラがこの未定義の動作を最適化できないようにしました。

ほとんどの場合、ターゲット環境によって異なります。これは、ソフトウェア例外をトリガしないであろうが、それはすることができるハードウェア例外をトリガする(ターゲットCPUに依存する)(整数、ゼロによる除算)、ソフトウェア例外をキャッチすることができる伝統的な方法で捕獲することができません。これは間違いなくx86CPU、および他のほとんどの(すべてではありません!)アーキテクチャに当てはまります。

ただし、プログラムをクラッシュさせるだけでなく、ハードウェア例外(発生した場合)を処理する方法があります。適用可能ないくつかの方法については、この投稿を参照してください。例外のキャッチ:ゼロ除算。それらはコンパイラごとに異なることに注意してください。


25
@Adrian動作が定義されていないため、どちらも完全に問題ありません。文字通り何でもOKです。
JesperJuhl19年

9
「C ++でのゼロ除算は未定義の動作です」->コンパイラはIEE754で浮動小数点型に対してこの最適化を行うことができないことに注意してください。dをNaNに設定する必要があります。
バトシェバ

6
@RichardHodges IEEE754で動作するコンパイラは、doubleの最適化を行うことはできません。NaNを生成する必要があります。
バトシェバ

2
@formerlyknownas:「UBを最適化する」という問題ではありません。「何かが起こる可能性がある」というのは依然として事実です。それ1は、生産が完全に有効なものであるということだけです。14684554を取得するのは、コンパイラがさらに最適化するためである必要があります。初期d==0条件を伝播するため、「これは1またはUBのいずれか」だけでなく、実際には「これはUB、期間」と結論付けることができます。したがって、定数をロードするコードを作成する必要はありません1
モニカの上に残さhmakholm

1
人々は常に最適化を防ぐために揮発性を提案していますが、揮発性の読み取りまたは書き込みを構成するものは実装によって定義されます。
philipxy

38

他の答えを補足するために、ゼロによる除算が未定義の動作であるという事実は、コンパイラーがそれが発生した場合に自由に何でもできることを意味します

  • コンパイラはそれを想定し0 / 0 == 1、それに応じて最適化する場合があります。それは事実上、ここで行ったように見えることです。
  • コンパイラーは、必要に応じて、それを想定してその値に0 / 0 == 42設定dすることもできます。
  • コンパイラは、の値dが不確定であると判断し、変数を初期化しないままにして、その値が、割り当てられたメモリに以前に書き込まれたものになります。コメントで他のコンパイラーで観察された予期しない値のいくつかは、それらのコンパイラーがこのようなことをしていることが原因である可能性があります。
  • コンパイラは、ゼロ除算が発生するたびに、プログラムを中止するか、例外を発生させることもできます。このプログラムの場合、コンパイラーはこれが常に発生することを判別できるため、コードを発行して例外を発生させ(または実行を完全に中止し)、残りの関数を到達不能コードとして扱うことができます。
  • ゼロ除算が発生したときに例外を発生させる代わりに、コンパイラーはプログラムを停止して、代わりにソリティアのゲームを開始することもできます。それも「未定義の振る舞い」の傘下にあります。
  • 原則として、コンパイラーは、ゼロ除算が発生するたびにコンピューターを爆発させるコードを発行することさえできます。これを禁止するC ++標準には何もありません。(ミサイルフライトコントローラーなどの特定の種類のアプリケーションでは、これは望ましい安全機能と見なされることもあります!)
  • さらに、この標準では、未定義の動作が「タイムトラベル」することを明示的に許可しているため、コンパイラーは、ゼロ除算が発生する前に、上記のいずれか(またはその他)を実行できます。基本的に、この標準では、プログラムの観察可能な動作が変更されない限り、コンパイラは操作を自由に並べ替えることができます。ただし、プログラムの実行によって未定義の動作が発生する場合は、最後の要件でさえ明示的に免除されます。したがって、事実上、ある時点で未定義の動作をトリガーするプログラム実行の動作全体は未定義です。
  • 上記の結果として、コンパイラは、未定義の動作が発生しないと単純に想定する場合もあります。これは、一部の入力で未定義の方法で動作するプログラムの許容される動作の1つは、入力が何かであるかのように動作することであるためです。それ以外の場合。つまりd、コンパイル時にの元の値がわからなかったとしても、コンパイラーはそれがゼロになることはないと想定し、それに応じてコードを最適化することができます。OPのコードの特定のケースでは、これは、と仮定するだけでコンパイラと実質的に区別できません0 / 0 == 1が、コンパイラは、たとえば、puts()inif (d == 0) puts("About to divide by zero!"); d /= d;が実行されないと仮定することもできます。

29

ゼロによる整数除算の動作は、C ++標準では定義されていません。例外をスローする必要はありません

(浮動小数点のゼロ除算も定義されていませんが、IEEE754で定義されています。)

コンパイラは最適化さd /= dれており、効果的にd = 1これを選択するのが妥当です。コードに未定義の動作がないと想定できるため、この最適化を行うことがdできます。つまり、ゼロになることはできません。


3
特に何か他のことが起こる可能性があることを明確にすることが重要です。IOWこの動作は信頼できません。
ハイド

2
コンパイラが「それはdおそらくゼロになることはできない」と仮定するのが合理的であると言うとき、コンパイラが次の行を認識しないと仮定しますint d = 0;か?:)
エイドリアンモール

6
コンパイラはそれを認識しますが、おそらく気にしません。このようなエッジケースのためにすでにクレイジーで複雑なコンパイラに必要な余分なコードの複雑さは、おそらくそれだけの価値はありません。
user45813 0119

1
@ user4581301両方を一緒に使用すると、ポイズニングされたブランチを検出できるため、より多くのコードをプルーニングできます。だからそれは役に立つでしょう。
デュプリケータ

3
したがって、「int d = 0; if(d == 0)printf( "d = zero \ n"); d / = d;」と記述した場合、コンパイラはprintfも削除できます。
gnasher7 2919

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.