2013-08-18 15 views
22

Penso che questo sia un bug nel compilatore C#.Perché il compilatore valuta il resto MinValue% -1 diverso dal runtime?

Considerate questo codice (all'interno di un metodo):

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

Compila senza errori (o avvisi). Sembra un insetto. Durante l'esecuzione, stampa 0 sulla console.

Poi, senza l'const, il codice:

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

Quando questo viene eseguito, si traduce correttamente in un OverflowException essere gettato.

La specifica del linguaggio C# menziona specificamente questo caso e dice che viene emesso un System.OverflowException. Non dipende dal contesto checked o unchecked sembra (anche il bug con gli operandi costanti in fase di compilazione per l'operatore restante è lo stesso con checked e unchecked).

Lo stesso errore si verifica con int (System.Int32), non solo long (System.Int64).

Per il confronto, il compilatore gestisce dividend/divisor con gli operandi const molto meglio di dividend % divisor.

Le mie domande:

ho ragione questo è un bug? Se sì, è un bug noto che non desiderano correggere (a causa della retrocompatibilità, anche se è piuttosto sciocco usare % -1 con una costante in fase di compilazione -1)? O dovremmo segnalarlo in modo che possano risolverlo nelle prossime versioni del compilatore C#?

+0

Menzionare @EricLippert potrebbe attirare la folla giusta per questa domanda :) –

+0

@Morten, a questo punto, potrebbe semplicemente guardare stupefatto dal suo posatoio a Coverity. ;) –

+0

Penso che dovresti mettere una taglia su questo perché mi irrita perché questo sta accadendo. Le specifiche dicono che qualsiasi espressione costante che può generare un'eccezione run-time dovrebbe causare un errore in fase di compilazione durante la compilazione !! –

risposta

19

Questo caso d'angolo è indirizzato in modo specifico nel compilatore.commenti più rilevanti e il codice nel 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); 

E:

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

anche il comportamento del lascito C++ versione del compilatore, che va tutta la strada fino alla versione 1. Dalla v1 SSCLI. 0 distribuzione, CLR/src file sorgente/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; 

Così conclusione da trarre che questo non è stato trascurato o dimenticato, almeno dai programmatori che w orked sul compilatore, potrebbe forse essere qualificato come linguaggio non sufficientemente preciso nelle specifiche del linguaggio C#. Maggiori informazioni sui problemi di runtime causati da questo colpo killer in this post.

4

Penso che non sia un bug; è piuttosto come calcola il compilatore C# % (è una supposizione). Sembra che il compilatore C# calcoli prima % per i numeri positivi, quindi applica il segno. Avendo Abs(long.MinValue + 1) == Abs(long.MaxValue) se scriviamo:

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

Ora vedremo 0 come la risposta che è corretto perché ora Abs(dividend) == Abs(long.MaxValue) che è nella gamma.

Perché funziona quando lo dichiariamo come valore const? (Ancora una supposizione) Sembra che il compilatore C# calcoli effettivamente l'espressione in fase di compilazione e non consideri il tipo di costante e agisca su di esso come un BigInteger o qualcosa (bug?). Perché se si dichiara una funzione come:

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

e chiamare Console.WriteLine(Compute(dividend, divisor)); avremo la stessa eccezione. E ancora, se si dichiara la costante in questo modo:

const long dividend = long.MinValue + 1; 

Non vorremmo ottenere l'eccezione.

+1

Sapevo già tutto questo. Si noti che la specifica dice: _Il risultato di 'x% y' è il valore prodotto da' x - (x/y) * y'. Se 'y' è zero, viene lanciata una' System.DivideByZeroException'. ↵↵ Se l'operando di sinistra è il valore più piccolo 'int' o' long' e l'operando di destra è '-1', viene lanciata una' System.OverflowException'. [...] _ È ovvio dalle tue osservazioni (e mie) che il compilatore non segue le specifiche quando il resto viene calcolato in fase di compilazione. Il runtime segue le specifiche. –

+0

Le mie scuse; Non ho letto le specifiche. Sì; L'ho visto ora anche nella mia risposta "agisce come un BigInteger o qualcosa del genere (bug?)". Hai ragione. –