3

私はずっと前に行ったコードを持っていました(私はVisual Studio 2003を使っていました)。今私はgccを使用していますが、いくつかの値がオーバーフローしています。私は何が起こっているかを見て、それは驚きです。一時的な値を特定のデータ型に格納する際の算術演算の標準規則は何ですか?

ワークス(出力= 85):

int b = 35000000; 
unsigned long a = 30000000; 
unsigned long n = (100 * a)/b; 

がいない作品(オーバーフロー):

int b = 35000000; 
int a = 30000000; 
unsigned long n = (100 * a)/b; 

がいない作品(

は、私は何が起こっているかお見せしましょうオーバーフロー):

int b = 35000000; 
unsigned long n = (100 * 30000000)/b; 

このshouすべて正しいと思います。今、何がバグですか?

unsigned long b= 35000000; 
unsigned long n = (100 * 30000000)/b; 

使いました!今それはしません。

実際にはまだMicrosoftのコンパイラで動作しますが、clangやgccでは動作しません。先に行くと、あなたがしたい場合は、別のコンパイラでコンパイル:http://rextester.com/BZU89042

  • Output = 85 - マイクロソフト(R)C/C++最適化コンパイラバージョン19.00.23506のx86
  • Output = 527049830640のために - G ++ 5.4.0
  • Output = 527049830640 - clang 3.8.0

これに関する標準的なC++ルールは何ですか?

+2

これは[整数リテラル](http://ja.cppreference.com/w/cpp/language/integer_literal)に関連していますか?標準によると、コンパイラは 'int'、' long int'、 'long long int'のいずれかを選択することができます(値が適合する場合) - GCCとclangは' int'を選択したようですVCは 'long long int'を選択しますか? – UnholySheep

+4

コンパイラは 'int'から' long'に(C++ 11以降) 'long long'にリテラル値が収まらない場合は、型のサイズを拡張する必要があります。詳細については、[参考文献](http://en.cppreference.com/w/cpp/language/integer_literal)を参照してください)。しかし、 '100'と' 30000000'の両方は 'int'に入り、乗算を行いますオーバーフロー。しかし、コンパイラがいつどこでどこをフォールディングするかによって、VC++の振る舞いを説明するフォールディングの後に型拡張(intからlong longまで)*を行う可能性があります。 –

+0

私の前のコメントはその動作を説明していますが、仕様書の内容についての質問には答えません。このことについて何も読んでいないと、定数フォールディングのような最適化が実装の詳細なので、実際に何も言わないということを推測します。したがって、その動作は*未定*です。それで、GCCとClangは、仕様の意図と手紙に従うことに近づくと思います。 –

答えて

6

整数リテラルのタイプを決定するためのルールが[lex.icon]からである:

整数リテラルのタイプは、その値を表現することができる。表7に対応するリストの最初のものです。接尾辞なしで、種類のリストは、その後、その後long intlong long intintで、

。次のようにこのパターンが定義されている通常の算術変換と呼ばれ、

::私たちは数学を行うときその後、ルールは常に[expr]に列挙されている「通常の算術変換」、である

(11.1) いずれかのオペランドがスコープ付き列挙型の場合、変換は実行されません。他のオペランドが同じ型を持たない場合、式は不正です。

(11.2) いずれかのオペランドがlong double型の場合、他方はlong doubleに変換されます。

(11.3) それ以外の場合は、いずれかのオペランドがdoubleの場合、もう一方はdoubleに変換されます。

(11。4) それ以外の場合、いずれかのオペランドが浮動小数点の場合、もう一方は浮動小数点に変換されます。

(11.5) そうでない場合、積分キャンペーンはその後、次の規則が促進オペランドに適用されるものとの両方operands.63上で実行されなければならない:両方のオペランドが同じ型を持っている場合

(11.5.1) それ以上の変換は必要ありません。それ以外の場合、両方のオペランドが符号付き整数型を持つか、両方とも符号なし整数型を持つ場合、より小さい整数変換ランクの型を持つオペランドは、より大きなランクを持つオペランド型に変換されます。

(11.5.3) それ以外の場合、符号なし整数型のオペランドが他のオペランドの型のランク以上のランクを持つ場合、符号付き整数型のオペランドは、符号なし整数型のオペランド

(11.5.4) 符号なし整数型のオペランドの型が、符号なし整数型のオペランドの型のすべての値を表すことができる場合、符号なし整数型のオペランドは、符号付き整数型のオペランドの型。

(11.5.5) それ以外の場合、両方のオペランドは、符号付き整数型のオペランドの型に対応する符号なし整数型に変換されます。


のはあなたの例を見ていきましょう:

int b = 35000000; 
unsigned long a = 30000000; 
unsigned long n = (100 * a)/b; 

100 * aの種類は、その結果を保持するのに十分な幅である、(なぜなら11.5.3の)unsigned longあるので、これは結構です。

乗算ではタイプがintになるため、残りは機能しません。最初のケースではaintと明示的に宣言され、残りの場合は30000000の型が(それが表現するのに十分小さいため)と明示的に宣言します。

注)intすることにより、この最後の例であること:

unsigned long b= 35000000; 
unsigned long n = (100 * 30000000)/b; 

それはbまたはnunsigned long宣言されていることは重要ではありません、表現(100 * 30000000)はまだ2ですは掛け合わされており、表現にかかわらずタイプintとなります。 gccとclangの両方がこのオーバーフローについて警告します。

これを修正するには、常にリテラルにサフィックスを追加します。この場合、100uまたは30000000uのいずれかがこのトリックを行います。これにより、型がunsigned int([lex.icon]による)のリテラルになり、乗算のタイプがオーバーフローしないunsigned int([expr] /11.5.3)になります。

4

遊びに来る標準的な規則であること:様々な整数型の

  1. 最低保証範囲、具体的に署名された整数と符号なしのロング。この場合、実際の範囲はコンパイラとターゲットとするプラットフォームによって異なります。 x86をターゲットとしているMSVCは、符号付き整数の場合は [-2^31..2^31]、符号なし整数の場合は[0..2^32]になります。 Windows以外の64ビットプラットフォームをターゲットとしたgccとclangは、符号なしintの場合と同じ範囲を与えますが、unsigned longの場合は[0..2^64]です。

  2. 符号付き整数オーバーフローが未定義のビヘイビアを呼び出すという事実[セクション3.9.1、段落4]。再グループ化が結果に影響しない場合、コンパイラは通常の可換性および連想性の規則に従って操作を再編成することができます。 [Section 1.9]符号付き整数オーバーフローが発生した場合、結果は何でもかまいませんが、このバーは実現するよりも低くなる可能性があります。具体

  3. 「通常の算術変換」:

[I]他のオペランドのタイプのランク以上ランクた符号なし整数型を持つオペランドF符号付き整数型のオペランドは、符号なし整数型のオペランドの型に変換しなければならない。 [第5節]

は、だからあなたの例与えられた:部分式100 * 30000000

unsigned long b = 35000000; 
unsigned long n = (100 * 30000000)/b; 

、両方のオペランドがsigned intです。 3000万人に100を掛けると、30億人になる。これは符号付き32ビット整数のオーバーフローなので、未定義のビヘイビアが呼び出されています。

明らかにMSVCが行うことは、32ビットパターンを2の補数として扱い、-1294967296を生成することです。だから我々は-1294967296/35000000ulを持っています。通常の算術変換ルールは、符号付きintを符号なしlongに変換するように指示します。 MSVCを使用すると、これは30億を戻し、整数除算の結果は85になります。

gccまたはclangではいくつかの点が異なります。まず、64ビットWindows以外のプラットフォームをターゲットにしていると思われます。つまり、符号なしlongは32ビットではなく64ビット幅です。第2に、これらのコンパイラは、 "法的なフィクション"に頼って(定数フォールディングのように)符号付き整数オーバーフローはありません。第3に、オーバーフローの結果が何であれ、除算の前に、より広い符号なしタイプに変換されるようになりました。

関連する問題