2013-08-18 7 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에 의존하지 않습니다 (나머지 연산자에 대한 컴파일 타임 상수 피연산자가있는 버그는 checkedunchecked과 동일합니다).

long (System.Int64)이 아닌 int (System.Int32)과 동일한 버그가 발생합니다.

는 비교를 위해, 컴파일러는 constdividend/divisordividend % 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; 

그래서 결론은 적어도 프로그래머가이 간과하거나 잊어되지 않았 음을 그리는 w 그 컴파일러에서 orked되면 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'가 0이면'System.DivideByZeroException'이 던져집니다. 왼쪽 피연산자가 가장 작은'int' 또는'long' 값이고 오른쪽 피연산자가'-1'이면'System.OverflowException'이 던져집니다. [...] _ 당신의 관찰 (및 광산)에서 컴파일러가 컴파일 타임에 나머지가 계산 될 때 스펙을 따르지 않는다는 것이 명백합니다. 런타임은 사양을 따릅니다. –

+0

사과드립니다. 나는 스펙을 읽지 않았다. 예; 나는 그것을 "BigInteger 또는 무언가 (bug?)"라고하는 것에 대한 나의 대답에서도 보았다. 당신이 올바른지. –