2013-08-18 12 views
22

Myślę, że wygląda to jak błąd w kompilatorze C#.Dlaczego kompilator ocenia pozostałą wartość MinValue% -1 inną niż środowisko wykonawcze?

Rozważmy następujący kod (wewnątrz metody):

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

kompiluje bez błędów (lub ostrzeżenia). Wygląda jak błąd. Po uruchomieniu wydrukuje 0 na konsoli.

Wtedy bez const, kod:

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

Gdy ta jest prowadzona, to poprawnie Skutkuje to OverflowException wyrzucane.

Specyfikacja języka C# wspomina o tym przypadku i mówi, że zostanie wyrzucony System.OverflowException. Nie zależy to od kontekstu, jak się wydaje (również błąd związany ze stałymi operandami w czasie kompilacji z pozostałym operatorem jest taki sam z checked i unchecked).

Ten sam błąd występuje z int (System.Int32), a nie tylko long (System.Int64).

Dla porównania kompilator obsługuje dividend/divisor z const operandami znacznie lepiej niż dividend % divisor.

Moje pytania:

Mam rację, to jest błąd? Jeśli tak, to czy jest to dobrze znany błąd, że nie chcą, aby naprawić (ze względu na kompatybilność wsteczną, nawet jeśli jest to raczej głupie korzystać % -1 z kompilacji stałą -1)? Czy powinniśmy to zgłosić, aby mogli go naprawić w nadchodzących wersjach kompilatora C#?

+0

Wspomnienie @EricLippert może przyciągnąć odpowiedni tłum na to pytanie :) –

+0

@Morten, w tym momencie może po prostu otumanić wzrokiem ze swojego okonia w Coverity. ;) –

+0

Myślę, że powinieneś dać nagrodę, ponieważ irytuje mnie, dlaczego tak się dzieje. Specyfikacja mówi, że każde wyrażenie stałe, które może rzucić wyjątek w czasie wykonywania, powinno spowodować kompilację podczas kompilacji !! –

Odpowiedz

19

Ten narożnik sprawa jest bardzo szczegółowo uregulowana w kompilator.Najistotniejsze komentarze i kod w 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); 

I:

// 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; 

także zachowanie starszego C++ wersji kompilatora, przechodząc przez całą drogę z powrotem do wersji 1. Z SSCLI V1. 0 dystrybucja, CLR/src plik źródłowy/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; 

więc wyciągnąć wniosek, że nie został pominięty lub zapomnieli o co najmniej przez programistów, które w orked na kompilatorze, może być zakwalifikowany jako niedostatecznie precyzyjny język w specyfikacji języka C#. Więcej informacji o problemach w środowisku wykonawczym spowodowanych przez ten problem z zabójcą w this post.

4

Myślę, że to nie jest błąd; to raczej jak kompilator C# wylicza % (Zgadnij). Wygląda na to, że kompilator C# najpierw oblicza % dla liczb dodatnich, a następnie stosuje znak. Mając Abs(long.MinValue + 1) == Abs(long.MaxValue) jeśli piszemy:

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

Teraz widzimy 0 jako odpowiedź, która jest prawidłowa, ponieważ teraz Abs(dividend) == Abs(long.MaxValue) który jest w zasięgu.

Dlaczego to działa, gdy deklarujemy to jako wartość const? (Ponownie domysły) Wydaje się, że kompilator C# faktycznie oblicza wyrażenie w czasie kompilacji i nie bierze pod uwagę typu stałej i nie działa na nim jako BigInteger czy coś (błąd?). Bo jeśli zadeklarować funkcji takich jak:

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

I zadzwonić Console.WriteLine(Compute(dividend, divisor)); będziemy mieli ten sam wyjątek. I znowu, jeśli zadeklarujemy stałą w następujący sposób:

const long dividend = long.MinValue + 1; 

Nie otrzymamy wyjątku.

+1

Już to wszystko wiedziałem. Zauważ, że specyfikacja mówi: _ Wynik 'x% y' jest wartością wytworzoną przez' x - (x/y) * y'. Jeśli "y" wynosi zero, zgłoszony zostanie wyjątek 'System.DivideByZeroException'. ↵↵ Jeśli lewy operand jest najmniejszą wartością "int" lub "long", a prawy operand to "-1", zostanie zgłoszony wyjątek "System.OverflowException". [...] _ Z twoich obserwacji (i moich) wynika, że ​​kompilator nie śledzi specyfikacji, gdy reszta jest obliczana podczas kompilacji. Środowisko wykonawcze działa zgodnie ze specyfikacją. –

+0

Moje przeprosiny; Nie czytałem specyfikacji. Tak; Widziałem to teraz również w mojej odpowiedzi "działa na nią jako na BigIntegera czy coś (bug?)". Masz rację. –

Powiązane problemy