2013-08-18 3 views
22

これはC#コンパイラのバグのようです。なぜコンパイラは実行時とは異なり、MinValue%-1の残りを評価しますか?

const long dividend = long.MinValue; 
const long divisor = -1L; 
Console.WriteLine(dividend % divisor); 

それがエラー(または警告)なしでコンパイル:

このコード(メソッド内)を考えます。 バグのようです。実行すると、0がコンソールに表示されます。

はその後constせずに、コード:これが実行されると

long dividend = long.MinValue; 
long divisor = -1L; 
Console.WriteLine(dividend % divisor); 

、それは正確に投げられるOverflowExceptionになります。

C#言語仕様では、このケースが具体的に言及されており、System.OverflowExceptionがスローされるものとしています。コンテキストcheckedまたはuncheckedに依存しません(残余演算子に対するコンパイル時定数オペランドのバグはcheckedおよびuncheckedと同じです)。

longSystem.Int64)だけでなく、intSystem.Int32)と同じバグが発生します。

比較のために、コンパイラは、constのオペランドを持つdividend/divisorを、dividend % divisorよりはるかに優れて処理します。

私の質問:

アム私は右、これはバグでしょうか?コンパイル時定数-1% -1を使用することがかなり愚かであっても、後方互換性のため、修正したくないよく知られているバグですか?あるいは、C#コンパイラの次期バージョンで修正できるように報告する必要がありますか?

+0

@EricLippertがこの質問のために右の群衆を描いているかもしれないと言っています:) –

+0

@Morten、この時点で、彼はCoverityの彼の農場からひどく目を引くかもしれません。 ;) –

+0

私はこのことがなぜ起こっているのか、それを私に苛立たせるので、あなたはこれに恩恵を与えるべきだと思います。仕様では、実行時例外をスローする可能性のある定数式は、コンパイル時にコンパイル時エラーを引き起こすことになります。 –

答えて

19

このコーナーケースは、コンパイラで特に扱われています。Roslyn sourceで最も関連性の高いコメントやコード:

// Although remainder and division always overflow at runtime with arguments int.MinValue/long.MinValue and -1  
// (regardless of checked context) the constant folding behavior is different.  
// Remainder never overflows at compile time while division does.  
newValue = FoldNeverOverflowBinaryOperators(kind, valueLeft, valueRight); 

そして:また

// MinValue % -1 always overflows at runtime but never at compile time  
case BinaryOperatorKind.IntRemainder: 
    return (valueRight.Int32Value != -1) ? valueLeft.Int32Value % valueRight.Int32Value : 0; 
case BinaryOperatorKind.LongRemainder: 
    return (valueRight.Int64Value != -1) ? valueLeft.Int64Value % valueRight.Int64Value : 0; 

SSCLI v1から戻ってバージョン1にすべての道を行くレガシーC++コンパイラのバージョンの振る舞い。 0分布、CLR/SRC/CSHARP/sccomp/fncbind.cppソースファイル:

case EK_MOD: 
    // if we don't check this, then 0x80000000 % -1 will cause an exception... 
    if (d2 == -1) { 
     result = 0; 
    } else { 
     result = d1 % d2; 
    } 
    break; 

だから、結論は、少なくともプログラマが、これは見落としたり、忘れていなかったことを描画することワットC#言語仕様では、正確な言語としては不十分である可能性があります。 this postのこのキラーポークによって引き起こされたランタイムのトラブルの詳細。

4

私はバグではないと思います。それはむしろC#コンパイラが%を計算する方法です(これは推測です)。それは、C#コンパイラが最初に%を正の数として計算してから、その符号を適用するようです。私たちが書く場合Abs(long.MinValue + 1) == Abs(long.MaxValue)を持つ:

static long dividend = long.MinValue + 1; 
static long divisor = -1L; 
Console.WriteLine(dividend % divisor); 

Abs(dividend) == Abs(long.MaxValue)範囲にあるので、今、私たちは正しい答えとして0が表示されます。

constという値で宣言すると、なぜ機能するのですか? (やはり推測)C#コンパイラはコンパイル時に実際に式を計算し、定数の型を考慮せずにBigIntegerか何か(バグ?)として動作するようです。

static long Compute(long l1, long l2) 
{ 
    return l1 % l2; 
} 

そして、我々は同じ例外が発生しますConsole.WriteLine(Compute(dividend, divisor));を呼び出します。私たちはのような機能を宣言した場合ので。そして、再びこのような定数を宣言すれば、

const long dividend = long.MinValue + 1; 

例外はありません。

+1

私はすでにそれをすべて知っていました。 specには次のように書かれています: 'x%y'の結果は' x - (x/y)* y'によって生成された値です。 'y'がゼロの場合、' System.DivideByZeroException'がスローされます。左オペランドが最小の 'int'または' long'値で、右のオペランドが '-1'の場合、' System.OverflowException'がスローされます。 [...] _あなたの所見(そして私の)からは、コンパイル時に余りが計算されるときにコンパイラが仕様に従わないことは明らかです。ランタイムは仕様に従います。 –

+0

私の謝罪;私は仕様を読んでいない。はい;私は今でも私の答えでそれを見ました。「BigIntegerや何か(バグ?あなたは正しいです。 –