2013-08-18 88 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。它看起來不取決於上下文checkedunchecked(編譯時操作數與餘數運算符的錯誤與checkedunchecked相同)。

同樣的問題發生在intSystem.Int32),而不僅僅是longSystem.Int64)。

作爲比較,編譯器處理dividend/divisorconst操作數比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; 

另外,傳統的C++編譯器的版本,將所有的方式回1版從SSCLI V1的行爲。 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; 

所以結論得出,這不是忽視或遺忘,至少由程序員使得w在編譯器上引用,它可能會被限定爲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); 

現在我們將看到0因爲這是正確的,因爲現在Abs(dividend) == Abs(long.MaxValue)這是範圍問題的答案。

爲什麼它在我們將其聲明爲const值時有效? (再次猜測)似乎C#編譯器實際上是在編譯時計算表達式的,並沒有考慮常量的類型並將其作爲BigInteger或其他東西(bug?)來處理。因爲如果我們像聲明一個函數:

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

並調用Console.WriteLine(Compute(dividend, divisor));我們會得到相同的異常。再次,如果我們聲明這樣的常數:

const long dividend = long.MinValue + 1; 

我們不會得到異常。

+1

我確實已經知道所有這些。請注意,規範說:_'x%y'的結果是'x - (x/y)* y'產生的值。如果'y'爲零,則拋出'System.DivideByZeroException'。如果左操作數是最小的「int」或「long」值,並且右操作數是「-1」,則拋出「System.OverflowException」。 [...] _從您(和我的)觀察中可以明顯看出,當編譯時計算餘數時,編譯器不遵循規範。運行時確實遵循規範。 –

+0

我的歉意;我沒有閱讀規範。是;我現在在我的回答中看到它「在BigInteger或其他方面的行爲(bug?)」。你是對的。 –

相關問題