web-dev-qa-db-ja.com

定数式のゼロ除算

定数式でゼロで除算すると、おもちゃのコンパイラがクラッシュします。

int x = 1 / 0;

この動作はCまたはC++標準で許可されていますか?

43
fredoverflow

の単なる存在1 / 0は、コンパイラのクラッシュを許可しません。せいぜい、式が評価されないこと、したがってその実行が指定された行に到達しないことを前提とすることが許可されています。

式の評価が保証されている場合、標準はプログラムまたはコンパイラに要件を課しません。 次に、コンパイラがクラッシュする可能性があります。

1/0は評価された場合のみUBです。

C11標準は 明示的な例1 / 0未評価時の動作が定義されています:

したがって、次の初期化では、

        static int i = 2 || 1 / 0;

式は、値が1の有効な整数定数式です。

セクション6.6、脚注118。

1/0は定数式ではありません。

セクション6.6 制約の下でのC11標準の

  1. 定数式には、評価、評価されない部分式の中に含まれている場合を除き、代入、増分、減分、関数呼び出し、またはカンマ演算子を含めないでください。
  2. 各定数式は、その型の表現可能な値の範囲内にある定数に評価されます。

1/0は、intで表現可能な値の範囲の定数に評価されないため、1/0は定数式ではありません。これは、割り当てを持たないというルールと同様に、定数式として数えるものに関するルールです。少なくともC++の場合、 Clangは1/0を定数式と見なさない であることがわかります。

prog.cc:3:18: error: constexpr variable 'x' must be initialized by a constant expression
   constexpr int x = 1/ 0 ;
                 ^   ~~~~

未評価の1/0がUBであることはあまり意味がありません。

(x == 0) ? x : 1 / xは、xが0で、1/xの評価がUBであっても、完全に明確に定義されています。もしそうなら(0 == 0) ? 0 : 1 / 0はUBでしたが、それはナンセンスです。

はい、ゼロによる除算は未定義の動作であり、CやC++標準のいずれもこのような場合の要件を課していません。この場合、少なくとも診断を発行する必要があると思います(下記を参照)。

標準を引用する前に、これは適合動作の場合もありますが、実装の品質は別の問題ですが、単に適合しているだけでは有用ではありません。私が知る限り、gcc、clang、Visual Studio、およびIntel(as tpg2114)チームは内部コンパイラエラーを考慮します(ICEs )報告すべきバグであること。現在のgccとclangはどちらも、提供されたフラグに関係なく、一見このケースに対して警告を生成することに注意してください。両方のオペランドがリテラル/定数である場合、ここでは、これを検出して診断を提供することはかなり簡単に思えます。この場合、clangは次の診断を生成します(実際に見る):

warning: division by zero is undefined [-Wdivision-by-zero]
int x = 1 / 0 ;
          ^ ~

ドラフトC11標準セクション6.5.5乗法演算子(emphasis mine)から:

/演算子の結果は、最初のオペランドを2番目のオペランドで除算した商です。 [...]2番目のオペランドの値がゼロの場合、動作は未定義です。

したがって、未定義の動作です。

C++標準ドラフトセクション5.6[expr.mul]は次のように述べています。

二項/演算子は商を生成します[...]/または%の2番目のオペランドがゼロの場合、動作は未定義です[...]

再び未定義の動作。

ドラフトC++標準とドラフトC標準はどちらも、未定義の動作について同様の定義を持っています。

[...]この国際標準が要件を課していない

フレーズは要件を課しません鼻の悪魔 を含むすべての動作を許可するようですどちらにも同様のメモがあり、次のように述べられています。

この国際標準が動作の明示的な定義を省略した場合、またはプログラムが誤った構成体または誤ったデータを使用した場合、未定義の動作が予想されます。 許容される未定義の動作は、予測できない結果で完全に状況を無視することから、環境の特性に応じて文書化された方法で変換またはプログラムの実行中に動作することまであります。診断メッセージの発行)、変換または実行の終了(診断メッセージの発行)

したがって、メモは規範的ではありませんが、翻訳中に終了する場合は、少なくとも診断を発行する必要があります。 terminatingという用語は定義されていないため、これが何を可能にするかを議論することは困難です。 clangとgccに診断なしのICEがあるケースを見たことがないと思います。

コードを実行する必要がありますか?

実行されないコードは未定義の動作を呼び出すことができますか? 少なくともCの場合、呼び出すために1 / 0を実行する必要があるという議論の余地があることがわかります未定義の動作。 C++の場合の悪い点は、behaviorの定義が存在しないため、Cの場合に使用される分析の一部をC++の場合に使用できないことです。

コンパイラがコードが実行されないことを証明できれば、as-ifプログラムが未定義の動作をしなかったと考えることができますが、私は考えていませんこれは証明可能で、妥当な動作です。

Cの観点から WG14欠陥レポート109 はこれをさらに明確にします。次のコード例を示します。

int foo()
{
  int i;
  i = (p1 > p2); /* Must this be "successfully translated"? */
  1/0; /* Must this be "successfully translated"? */
  return 0;
} 

含まれている応答:

さらに、特定のプログラムのすべての実行が未定義の動作になる場合、特定のプログラムは厳密に準拠していません。
そのプログラムを実行すると、未定義の動作が発生する可能性があるため、厳密に準拠しているプログラムの翻訳に失敗してはなりません。 fooは決して呼び出されない可能性があるため、指定された例は、準拠する実装によって正常に変換される必要があります。

したがって、Cの場合、未定義の動作を呼び出すコードが実行されることが保証されない限り、コンパイラーはプログラムを正常に変換する必要があります。

C++ constexprケース

xがconstexpr変数の場合:

constexpr int x = 1 / 0 ;

これは形式が正しくなく、gccは警告を生成し、clangはエラーを生成します(それをライブで参照):

error: constexpr variable 'x' must be initialized by a constant expression
constexpr int x = 1/ 0 ;
             ^   ~~~~
note: division by zero
constexpr int x = 1/ 0 ;
                  ^
warning: division by zero is undefined [-Wdivision-by-zero]
constexpr int x = 1/ 0 ;
                  ^ ~

ゼロによる除算は定義されていないことに注意してください

ドラフトC++標準セクション5.19定数式[expr.const]は次のように述べています。

条件式eは、抽象マシンのルール(1.9)に従ってeの評価が次の式のいずれかを評価しない限り、コア定数式です。

次の箇条書きが含まれています。

未定義の動作をする演算[注意:たとえば、符号付き整数オーバーフロー(条項5)、特定のポインター演算(5.7)、ゼロ除算(5.6)、または特定のシフト演算(5.8)—終了の注意]を含む)。

1/0はC11の定数式ですか

1 / 0はC11の定数式ではありません。これは6.6定数式から確認できます。

各定数式は、その型の表現可能な値の範囲内にある定数に評価されます。

ただし、次のことが可能です。

実装は、他の形式の定数式を受け入れる場合があります。

したがって、1 / 0はCまたはC++の定数式ではありませんが、定数式を必要とするコンテキストでは使用されていないため、答えは変わりません。両方のオペランドがリテラルであるため、OPが1 / 0を定数の折りたたみに使用できることを意味していると思います。これもクラッシュを説明します。

40
Shafik Yaghmour

C標準ドラフト(N1570)から:

6.5.5乗法演算子

...

  1. /演算子の結果は、最初のオペランドを2番目のオペランドで除算した商です。 %演算子の結果が余りです。どちらの演算でも、2番目のオペランドの値がゼロの場合、動作は未定義です。

第3章の未定義の動作について。用語、定義、記号:

3.4.3

  1. 未定義の動作
    動作、移植性のない、またはエラーのあるプログラム構成、またはこの国際標準が要件を課さないエラーのあるデータの使用時
  2. 注未定義の動作の可能性は、状況を完全に無視して予測できない結果をもたらすことから、翻訳中に動作するまたはプログラムの実行環境に特徴的な文書化された方法で(診断メッセージの発行の有無にかかわらず) )、変換または実行を終了する(診断メッセージを発行する)。

したがって、コンパイラのクラッシュは許可されます。

15
user694733

他の人はすでに規格の関連テキストについて言及しているので、私はそれを繰り返すつもりはありません。

私のCコンパイラの式評価関数は、逆ポーランド記法(値の配列(数値と識別子)および演算子)の式を取り、2つのものを返します。式が定数に評価されるかどうかのフラグと、定数の場合は値(それ以外の場合は0)。結果が定数の場合、RPN全体がその定数にまで減少します。 1/0は定数整数値に評価されないため、定数式ではありません。 RPNは1/0の間削減されず、そのまま残ります。

Cでは、静的変数は定数値でのみ初期化できます。したがって、コンパイラーは、静的変数の初期化子が定数ではないことを検出するとエラーになります。自動ストレージの変数は、非定数式で初期化できます。この場合、コンパイラーは1/0を評価するコードを生成します(この式のRPNがまだあります)。実行時にこのコードに到達すると、言語標準で規定されているとおりにUBが発生します。 [x86では、このUBはゼロ除算CPU例外の形式をとりますが、MIPSでは、このUBは誤った商値を生成します(CPUにはゼロ除算例外はありません)。]

私のコンパイラーは、||-式および&&-式での短絡を適切にサポートしています。したがって、論理演算子の右側のオペランドが定数かどうかに関係なく、1 || 1/0を1と評価し、0 && 1/0を0と評価します。式を評価する関数は、これらの演算子の右側のオペランドを評価する必要がないときに(演算子とともに)削除するため、1 || 1/01 != 0に変換されます(&&および||のオペランドは1)を生成する0)との比較。0 && 1/0は0を生成する0 != 0に変換されます。

注意すべきもう1つのケースは、INT_MIN / -1INT_MIN % -1です(より大きな整数型には同じです)。商は符号付き整数として表現できません(2の補数の符号付き整数の場合、これはすべての最近のCPUで使用されているものです)。これもUBです(実行時にx86で同じゼロ除算例外が発生します)。 。このケースも同様に扱います。この式は静的ストレージの変数を初期化できず、論理&&/||で評価されない場合は破棄されますオペレーター。自動変数を初期化でき、実行時にUBにつながる可能性があります。

そのような分割が発生した場合にも警告を出します。

2
Alexey Frunze