Warum ist der Aufzählungswert eines mehrdimensionalen Arrays nicht gleich sich selbst?

151

Erwägen:

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 kann das erklärt werden? Es tritt in Debug-Builds in Visual Studio 2015 auf, wenn es in der x86-JIT ausgeführt wird. Ein Release-Build oder eine Ausführung in der x64-JIT gibt True wie erwartet aus.

So reproduzieren Sie über die Befehlszeile:

csc Test.cs /platform:x86 /debug

( /debug:pdbonly, /debug:portableUnd /debug:fullreproduzieren auch.)

Shingo
quelle
2
ideone.com/li3EzY es ist wahr. Weitere Informationen zu .net-Version, IDE, Compiler hinzufügen
Backs
1
Hier gilt das gleiche. Nachdem ich mit den Projekteinstellungen herumgespielt hatte, stellte ich fest, dass das Deaktivieren des Kontrollkästchens "32-Bit bevorzugen" auf der Registerkarte "Erstellen" dazu führt, dass es wie beabsichtigt funktioniert - und true zurückgibt. Für mich sieht es also nach einem WoW64-Problem aus.
Dmitry Rotay
2
Es scheint, dass Sie auf einen Fehler im Framework hingewiesen haben.
Fabien PERRONNET
1
Interessanterweise läuft der kaputte Code durch ildasmund ilasm"repariert" ihn dann ...
Jon Skeet
2
Die /debug=IMPLFlagge lässt es gebrochen; /debug=OPT"behebt" es.
Jon Skeet

Antworten:

163

Sie haben einen Fehler bei der Codegenerierung im .NET 4 x86-Jitter gefunden. Es ist sehr ungewöhnlich und schlägt nur fehl, wenn der Code nicht optimiert ist. Der Maschinencode sieht folgendermaßen 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 Trottel-Affäre mit vielen temporären und Code-Duplikationen, die für nicht optimierten Code normal ist. Bemerkenswert ist der Befehl bei 013F04B8, bei dem die notwendige Umwandlung von sbyte in eine 32-Bit-Ganzzahl erfolgt. Die Array-Getter-Hilfsfunktion hat 0x0000000FF zurückgegeben, was State.BUG entspricht. Diese Funktion muss in -1 (0xFFFFFFFF) konvertiert werden, bevor der Wert verglichen werden kann. Der MOVSX-Befehl ist ein Sign eXtension-Befehl.

Dasselbe passiert erneut bei 013F04CC, aber dieses Mal gibt es keine MOVSX-Anweisung, um dieselbe Konvertierung durchzuführen. Dort fallen die Chips herunter, der CMP-Befehl vergleicht 0xFFFFFFFF mit 0x000000FF und das ist falsch. Dies ist also ein Auslassungsfehler. Der Codegenerator konnte MOVSX nicht erneut ausgeben, um dieselbe Konvertierung von Sbyte in Int durchzuführen.

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

Der wahrscheinliche Grund, warum dieser Fehler so lange unentdeckt blieb, ist die Verwendung von sbyte als Basistyp der Aufzählung. Sehr selten zu tun. Die Verwendung eines mehrdimensionalen Arrays ist ebenfalls von entscheidender Bedeutung. Die Kombination ist fatal.

Ansonsten ein ziemlich kritischer Fehler, würde ich sagen. Wie weit verbreitet es sein könnte, ist schwer zu erraten. Ich muss nur den Jitter 4.6.1 x86 testen. Der x64- und der 3.5 x86-Jitter erzeugen sehr unterschiedlichen Code und vermeiden diesen Fehler. Die vorübergehende Problemumgehung, um fortzufahren, besteht darin, sbyte als Enum-Basistyp zu entfernen und es als Standard int zu lassen , sodass keine Vorzeichenerweiterung erforderlich ist.

Sie können den Fehler unter connect.microsoft.com einreichen. Die Verknüpfung mit diesen Fragen und Antworten sollte ausreichen, um ihnen alles zu sagen, was sie wissen müssen. Lassen Sie mich wissen, wenn Sie sich nicht die Zeit nehmen möchten, und ich werde mich darum kümmern.

Hans Passant
quelle
33
Gute, solide Daten mit der genauen Ursache eines so seltsamen Problems, immer eine Freude zu lesen, +1.
Lasse V. Karlsen
11
Bitte posten Sie einen Link zum Artikel connect.microsoft.com, damit wir dafür stimmen können.
Hans Passant
Ich gehe davon aus, dass die Verwendung von byteanstelle von auch sbytein Ordnung sein sollte und möglicherweise vorzuziehen ist, wenn der echte Code beispielsweise mit einem ORM verwendet wird, bei dem Ihre Flags in der Datenbank keinen zusätzlichen Speicherplatz beanspruchen sollen.
Voo
6
Ich würde den Fehler auf dotnet / coreclr posten, anstatt eine Verbindung herzustellen. Sie werden direkt zu den JIT-Entwicklern gelangen.
Lucas Trzesniewski
8
Ich bin Entwickler im JIT-Team von Microsoft. Ich habe den Fehler reproduziert und intern ein Problem dafür geöffnet (der Versand von x86 JIT ist auf Github noch nicht offen). In Bezug auf den Zeitpunkt, zu dem dies behoben werden soll, gehe ich davon aus, dass dieses Update in der nächsten Hauptversion der Tools enthalten sein wird. Wenn dieser Fehler geschäftliche Auswirkungen hat und Sie früher eine Lösung benötigen, reichen Sie bitte das Problem connect (connect.microsoft.com) ein, damit wir die Auswirkungen und die Alternativen sehen können, die wir haben, um schneller eine Lösung für Sie zu finden.
Russell C. Hadley
8

Betrachten wir die Erklärung von OP:

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

Da der Fehler nur auftritt, wenn er BUGnegativ ist (von -128 bis -1) und State eine Aufzählung von vorzeichenbehafteten Bytes ist begann ich anzunehmen, dass irgendwo ein Besetzungsproblem aufgetreten ist.

Wenn Sie dies ausführen:

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

es wird ausgegeben:

255

-1

FEHLER

255

Aus einem Grund, den ich (ab sofort) ignoriere s[0, 0], wird er vor der Auswertung in ein Byte umgewandelt, und deshalb wird behauptet, dass dies a == s[0,0]falsch ist.

Thomas Ayoub
quelle