Perché il valore di enumerazione da un array multidimensionale non è uguale a se stesso?


151

Tener conto di:

using System;

public class Test
{
    enum State : sbyte { OK = 0, BUG = -1 }

    static void Main(string[] args)
    {
        var s = new State[1, 1];
        s[0, 0] = State.BUG;
        State a = s[0, 0];
        Console.WriteLine(a == s[0, 0]); // False
    }
}

Come può essere spiegato? Si verifica nelle build di debug in Visual Studio 2015 durante l'esecuzione in JIT x86. Una build di rilascio o in esecuzione in JIT x64 stampa True come previsto.

Per riprodurre dalla riga di comando:

csc Test.cs /platform:x86 /debug

( /debug:pdbonly, /debug:portablee /debug:fullanche riprodurre.)


2
ideone.com/li3EzY è vero. aggiungere ulteriori informazioni sulla versione .net, IDE, compilatore
Backs

1
Anch'io. Ma dopo aver armeggiato con le impostazioni del progetto ho capito che deselezionando la casella di controllo "Prefer 32 bit" nella scheda "Build" lo fa funzionare come previsto - restituendo vero. Quindi, a me sembra un problema di WoW64.
Dmitry Rotay,

2
Sembra che tu abbia indicato un bug nel framework.
Fabien PERRONNET,

1
È interessante notare che il codice non funzionante viene eseguito ildasme quindi ilasm"risolto" ...
Jon Skeet

2
La /debug=IMPLbandiera la lascia rotta; /debug=OPTlo "risolve".
Jon Skeet,

Risposte:


163

È stato trovato un bug di generazione del codice nel jitter .NET 4 x86. È molto insolito, fallisce solo quando il codice non è ottimizzato. Il codice macchina è simile al seguente:

        State a = s[0, 0];
013F04A9  push        0                            ; index 2 = 0
013F04AB  mov         ecx,dword ptr [ebp-40h]      ; s[] reference
013F04AE  xor         edx,edx                      ; index 1 = 0
013F04B0  call        013F0058                     ; eax = s[0, 0]
013F04B5  mov         dword ptr [ebp-4Ch],eax      ; $temp1 = eax 
013F04B8  movsx       eax,byte ptr [ebp-4Ch]       ; convert sbyte to int
013F04BC  mov         dword ptr [ebp-44h],eax      ; a = s[0, 0]
        Console.WriteLine(a == s[0, 0]); // False
013F04BF  mov         eax,dword ptr [ebp-44h]      ; a
013F04C2  mov         dword ptr [ebp-50h],eax      ; $temp2 = a
013F04C5  push        0                            ; index 2 = 0
013F04C7  mov         ecx,dword ptr [ebp-40h]      ; s[] reference 
013F04CA  xor         edx,edx                      ; index 1 = 0
013F04CC  call        013F0058                     ; eax = s[0, 0]
013F04D1  mov         dword ptr [ebp-54h],eax      ; $temp3 = eax 
                                               ; <=== Bug here!
013F04D4  mov         eax,dword ptr [ebp-50h]      ; a == s[0, 0] 
013F04D7  cmp         eax,dword ptr [ebp-54h]  
013F04DA  sete        cl  
013F04DD  movzx       ecx,cl  
013F04E0  call        731C28F4  

Un affare plodding con molti provvisori e duplicazione del codice, questo è normale per il codice non ottimizzato. L'istruzione su 013F04B8 è notevole, ovvero dove si verifica la conversione necessaria da sbyte a un numero intero a 32 bit. La funzione di aiuto getter dell'array ha restituito 0x0000000FF, uguale a State.BUG, e deve essere convertito in -1 (0xFFFFFFFF) prima di poter confrontare il valore. L'istruzione MOVSX è un'istruzione Sign eXtension.

La stessa cosa accade di nuovo a 013F04CC, ma questa volta non c'è alcuna istruzione MOVSX per fare la stessa conversione. Ecco dove cadono i chip, l'istruzione CMP confronta 0xFFFFFFFF con 0x000000FF e questo è falso. Quindi questo è un errore di omissione, il generatore di codice non è riuscito a emettere di nuovo MOVSX per eseguire lo stesso sbyte nella conversione int.

Ciò che è particolarmente insolito di questo bug è che funziona correttamente quando si abilita l'ottimizzatore, ora sa usare MOVSX in entrambi i casi.

La probabile ragione per cui questo bug non è stato rilevato per così tanto tempo è l'uso di sbyte come tipo base dell'enum. Abbastanza raro da fare. Anche l'utilizzo di un array multidimensionale è strumentale, la combinazione è fatale.

Altrimenti direi un bug piuttosto critico. Quanto sia diffuso è difficile da indovinare, ho solo il jitter 4.6.1 x86 da testare. Il jitter x64 e 3.5 x86 generano un codice molto diverso ed evitano questo errore. La soluzione temporanea per continuare è rimuovere sbyte come tipo di base enum e lasciarlo come predefinito, int , quindi non è necessaria l'estensione del segno.

Puoi segnalare il bug su connect.microsoft.com, il collegamento a questo Q + A dovrebbe essere sufficiente per dire loro tutto ciò che devono sapere. Fammi sapere se non vuoi prenderti il ​​tempo e me ne occuperò io.


33
Dati buoni e solidi con la causa esatta di un problema così strano, sempre un piacere leggere, +1.
Lasse V. Karlsen,

11
Pubblica un link all'articolo connect.microsoft.com in modo che possiamo votare per questo.
Hans Passant,

Suppongo che usare byteinvece di sbytedovrebbe andare bene e potrebbe essere preferibile se il codice reale viene utilizzato con diciamo un ORM in cui non si desidera che le bandiere nel database occupino spazio aggiuntivo.
Voo,

6
Pubblicherei il bug su dotnet / coreclr piuttosto che connettersi, arriverai direttamente agli sviluppatori JIT.
Lucas Trzesniewski,

8
Sono uno sviluppatore del team JIT di Microsoft. Ho riprodotto il bug e ho aperto un problema internamente (la spedizione di x86 JIT non è ancora all'aperto su github). In termini di tempistica di quando questo verrà risolto, prevedo che questa correzione verrà inclusa nella prossima versione principale degli strumenti. Se questo bug ha un impatto aziendale e hai bisogno di una correzione in precedenza, ti preghiamo di presentare il problema di connessione (connect.microsoft.com) in modo che possiamo avere un impatto e quali alternative abbiamo per ottenere una correzione più velocemente.
Russell C. Hadley,

8

Consideriamo la dichiarazione di OP:

enum State : sbyte { OK = 0, BUG = -1 }

Poiché il bug si verifica solo quando BUGè negativo (da -128 a -1) e State è un enum di byte con segno, ho iniziato a supporre che ci fosse un problema di trasmissione da qualche parte.

Se esegui questo:

Console.WriteLine((sbyte)s[0, 0]);
Console.WriteLine((sbyte)State.BUG);
Console.WriteLine(s[0, 0]);
unchecked
{
    Console.WriteLine((byte) State.BUG);
}

produrrà:

255

-1

ERRORE

255

Per un motivo che ignoro (fin d'ora) s[0, 0] viene trasmesso a un byte prima della valutazione ed è per questo che afferma che a == s[0,0]è falso.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.