2016-04-13 9 views
150

Bedenken Sie:Warum ist der Aufzählungswert aus einem multidimensionalen Array nicht gleich?

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 
    } 
} 

Wie dies zu erklären? Es tritt in Debug-Build in Visual Studio 2015 auf, wenn es in dem x86-JIT ausgeführt wird. Ein Release, das im x64 JIT erstellt oder ausgeführt wird, gibt True wie erwartet aus.

von der Kommandozeile zu reproduzieren: (. /debug:pdbonly, /debug:portable und /debug:full auch reproduzieren)

csc Test.cs /platform:x86 /debug 

+2

https://ideone.com/li3EzY es ist wahr. Fügen Sie weitere Informationen über .NET-Version, IDE, Compiler – Backs

+0

hinzu Ich sehe diese Rückgabe falsch unter einem Debug-Build, aber kein Release-Build, Targeting .Net 4.6.2 in VS2015. –

+1

Das gleiche hier. Aber nachdem ich mit den Projekteinstellungen herumgespielt hatte, fand ich heraus, dass das Häkchen bei "Prefer 32 bit" in der "Build" -Registerkarte wie gewünscht funktioniert - und "True" zurückgibt. Es sieht für mich wie ein WoW64-Problem aus. –

Antwort

162

Sie haben einen Code-Generierungsfehler im .NET 4 x86-Jitter gefunden. Es ist sehr ungewöhnlich, es schlägt nur fehl, wenn der Code nicht optimiert ist. Der Maschinencode sieht wie folgt aus:

 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 

Eine Plackerei Angelegenheit mit vielen Provisorien und Code-Duplizierung, das ist normal, nicht optimierten Code. Bemerkenswert ist der Befehl bei 013F04B8, bei dem die notwendige Konvertierung von sbyte in eine 32-Bit-Ganzzahl erfolgt. Die Array-Getter-Hilfsfunktion hat 0x0000000FF zurückgegeben, entspricht State.BUG und muss in -1 (0xFFFFFFFF) konvertiert werden, bevor der Wert verglichen werden kann. Die MOVSX-Anweisung ist eine Sign eXtension-Anweisung.

Das gleiche passiert wieder bei 013F04CC, aber dieses Mal gibt es keine MOVSX Anweisung, um die gleiche Konvertierung zu machen. Dort fallen die Chips, der CMP-Befehl vergleicht 0xFFFFFFFF mit 0x000000FF und das ist falsch. Dies ist also ein Fehler der Auslassung, der Code-Generator konnte MOVSX nicht erneut ausgeben, um die gleiche Umwandlung von sbyte nach int durchzuführen.

Was an diesem Fehler besonders ungewöhnlich ist, ist, dass dies korrekt funktioniert, wenn Sie den Optimierer aktivieren, er kann jetzt in beiden Fällen MOVSX verwenden.

Der mögliche Grund, dass dieser Fehler so lange unentdeckt blieb, ist die Verwendung von sbyte als Basistyp der Enumeration. Sehr selten zu tun.Die Verwendung eines multidimensionalen Arrays ist ebenfalls instrumentell, die Kombination ist fatal.

Sonst ein ziemlich kritischer Fehler würde ich sagen. Wie weit verbreitet es ist, ist schwer zu erraten, ich habe nur den 4.6.1 x86 Jitter zu testen. Der x64- und der 3,5x86-Jitter erzeugen einen sehr unterschiedlichen Code und vermeiden diesen Fehler. Die temporäre Problemumgehung zum Fortfahren besteht darin, sbyte als Enum-Basistyp zu entfernen und den Standardwert int zu definieren, sodass keine Zeichenerweiterung erforderlich ist.

Sie können den Fehler bei connect.microsoft.com ablegen, die Verknüpfung zu diesem Q + A sollte ausreichen, um ihnen alles zu erzählen, was sie wissen müssen. Lass es mich wissen, wenn du dir nicht die Zeit nehmen willst und ich werde mich darum kümmern.

+33

Gute, solide Daten mit der genauen Ursache für ein so seltsames Problem, immer ein Vergnügen zu lesen, +1. –

+11

Bitte poste einen Link zum connect.microsoft.com-Artikel, damit wir dafür stimmen können. –

+0

Ich nehme an, 'byte' anstelle von' sbyte' sollte auch in Ordnung sein und könnte vorzuziehen sein, wenn der reale Code mit einem ORM verwendet wird, bei dem Ihre Flags in der Datenbank keinen zusätzlichen Speicherplatz benötigen. – Voo

8

Lassen Sie uns OP Erklärung betrachten:

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

Da nur der Fehler tritt auf, wenn BUG ist negativ (von -128 bis -1) und State ist eine Enum von signierten Byte Ich begann zu vermuten, dass es irgendwo ein Problem gab.

Wenn Sie führen dies:

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

es folgende Ausgabe:

-1

BUG

Aus einem Grund, den ich (ab sofort)s[0, 0] wird auf ein Byte vor der Auswertung ignorieren gegossen und deshalb ist es behauptet, dass a == s[0,0] falsch ist.