Ich wollte die StackOverflow-Community anpingen, um zu sehen, ob ich mit diesem einfachen Stück C # -Code den Verstand verliere oder nicht.
Ich entwickle unter Windows 7 und erstelle dies in .NET 4.0, x64 Debug.
Ich habe folgenden Code:
static void Main()
{
double? y = 1D;
double? z = 2D;
double? x;
x = y + z;
}
Wenn ich debugge und einen Haltepunkt auf die geschweifte Endklammer setze, erwarte ich im Überwachungsfenster und im Sofortfenster x = 3. x = null stattdessen.
Wenn ich in x86 debugge, scheinen die Dinge gut zu funktionieren. Stimmt etwas mit dem x64-Compiler nicht oder stimmt etwas mit mir nicht?
Antworten:
Douglas 'Antwort bezüglich des JIT-optimierenden toten Codes ist richtig ( sowohl die x86- als auch die x64-Compiler werden dies tun). Wenn der JIT-Compiler jedoch den toten Code optimieren würde, wäre dies sofort offensichtlich, da er
x
nicht einmal im Fenster "Locals" angezeigt würde. Darüber hinaus würden Sie beim Versuch, darauf zuzugreifen, bei der Überwachung und dem Sofortfenster stattdessen einen Fehler erhalten: "Der Name 'x' existiert im aktuellen Kontext nicht". Das haben Sie nicht als passiert beschrieben.Was Sie sehen, ist tatsächlich ein Fehler in Visual Studio 2010.
Zuerst habe ich versucht, dieses Problem auf meinem Hauptcomputer zu reproduzieren: Win7x64 und VS2012.
x
Ist für .NET 4.0-Ziele gleich 3.0D, wenn die schließende geschweifte Klammer unterbrochen wird. Ich habe mich entschlossen, auch .NET 3.5-Ziele auszuprobieren, und war damitx
auch auf 3.0D eingestellt, nicht auf null.Da ich dieses Problem nicht perfekt reproduzieren kann, da ich .NET 4.5 auf .NET 4.0 installiert habe, habe ich eine virtuelle Maschine hochgefahren und VS2010 darauf installiert.
Hier konnte ich das Problem reproduzieren. Mit einem Haltepunkt in der schließenden geschweiften Klammer der
Main
Methode, sowohl im Überwachungsfenster als auch im Fenster der Einheimischen, sah ich, dass dies der Fallx
warnull
. Hier beginnt es interessant zu werden. Ich habe stattdessen auf die v2.0-Laufzeit abgezielt und festgestellt, dass sie auch dort null ist. Dies kann sicherlich nicht der Fall sein, da ich auf meinem anderen Computer dieselbe Version der .NET 2.0-Laufzeit habe, diex
mit einem Wert von erfolgreich angezeigt wurde3.0D
.Also, was passiert dann? Nach einigem Stöbern in Windbg fand ich das Problem:
VS2010 zeigt Ihnen den Wert von x an, bevor er tatsächlich zugewiesen wurde .
Ich weiß, dass es nicht so aussieht, da der Anweisungszeiger hinter der
x = y + z
Zeile steht. Sie können dies selbst testen, indem Sie der Methode einige Codezeilen hinzufügen:double? y = 1D; double? z = 2D; double? x; x = y + z; Console.WriteLine(); // Don't reference x here, still leave it as dead code
Mit einem Haltepunkt auf der letzten geschweiften Klammer werden die Einheimischen und das Beobachtungsfenster
x
gleich angezeigt3.0D
. Wenn Sie jedoch Schritt durch den Code, werden Sie feststellen , dass VS2010 zeigt nicht ,x
wie erst zugewiesen werden , nachdem Sie durch die gestufte habenConsole.WriteLine()
.Ich weiß nicht, ob dieser Fehler jemals an Microsoft Connect gemeldet wurde, aber Sie können dies anhand dieses Codes als Beispiel tun. Es wurde jedoch eindeutig in VS2012 behoben, daher bin ich mir nicht sicher, ob es ein Update geben wird, um dies zu beheben oder nicht.
Hier ist, was tatsächlich in der JIT und VS2010 passiert
Mit dem Originalcode können wir sehen, was VS tut und warum es falsch ist. Wir können auch sehen, dass die
x
Variable nicht weg optimiert wird (es sei denn, Sie haben die zu kompilierende Assembly mit aktivierten Optimierungen markiert).Schauen wir uns zunächst die lokalen Variablendefinitionen der IL an:
.locals init ( [0] valuetype [mscorlib]System.Nullable`1<float64> y, [1] valuetype [mscorlib]System.Nullable`1<float64> z, [2] valuetype [mscorlib]System.Nullable`1<float64> x, [3] valuetype [mscorlib]System.Nullable`1<float64> CS$0$0000, [4] valuetype [mscorlib]System.Nullable`1<float64> CS$0$0001, [5] valuetype [mscorlib]System.Nullable`1<float64> CS$0$0002)
Dies ist eine normale Ausgabe im Debug-Modus. Visual Studio definiert doppelte lokale Variablen, die bei Zuweisungen verwendet werden, und fügt dann zusätzliche IL-Befehle hinzu, um sie von der CS * -Variablen in die jeweilige benutzerdefinierte lokale Variable zu kopieren. Hier ist der entsprechende IL-Code, der dies zeigt:
// For the line x = y + z L_0045: ldloca.s CS$0$0000 // earlier, y was stloc.3 (CS$0$0000) L_0047: call instance !0 [mscorlib]System.Nullable`1<float64>::GetValueOrDefault() L_004c: conv.r8 // Convert to a double L_004d: ldloca.s CS$0$0001 // earlier, z was stloc.s CS$0$0001 L_004f: call instance !0 [mscorlib]System.Nullable`1<float64>::GetValueOrDefault() L_0054: conv.r8 // Convert to a double L_0055: add // Add them together L_0056: newobj instance void [mscorlib]System.Nullable`1<float64>::.ctor(!0) // Create a new nulable L_005b: nop // NOPs are placed in for debugging purposes L_005c: stloc.2 // Save the newly created nullable into `x` L_005d: ret
Lassen Sie uns mit WinDbg ein tieferes Debugging durchführen:
Wenn Sie die Anwendung in VS2010 debuggen und am Ende der Methode einen Haltepunkt belassen, können wir WinDbg problemlos im nicht-invasiven Modus anhängen.
Hier ist der Frame für die
Main
Methode im Aufrufstapel. Wir kümmern uns um die IP (Anweisungszeiger).Wenn wir den nativen Maschinencode für die
Main
Methode anzeigen , können wir sehen, welche Anweisungen zum Zeitpunkt der Unterbrechung der Ausführung durch VS ausgeführt wurden:Unter Verwendung der aktuellen IP, von der wir
!clrstack
in erhalten habenMain
, sehen wir, dass die Ausführung der Anweisung direkt nach dem Aufruf desSystem.Nullable<double>
Konstruktors angehalten wurde . (int 3
ist der Interrupt, der von Debuggern verwendet wird, um die Ausführung zu stoppen.) Ich habe diese Zeile mit * umgeben, und Sie können die Zeile auchL_0056
in der IL abgleichen.Die folgende x64-Assembly weist sie tatsächlich der lokalen Variablen zu
x
. Unser Anweisungszeiger hat diesen Code noch nicht ausgeführt, sodass VS2010 vorzeitig unterbrochen wird, bevor diex
Variable vom nativen Code zugewiesen wurde.BEARBEITEN: In x64 wird die
int 3
Anweisung vor dem Zuweisungscode platziert, wie Sie oben sehen können. In x86 wird diese Anweisung nach dem Zuweisungscode platziert. Das erklärt, warum VS nur in x64 früh bricht. Es ist schwer zu sagen, ob dies an Visual Studio oder dem JIT-Compiler liegt. Ich bin nicht sicher, welche Anwendung Haltepunkt-Hooks einfügt.quelle
Der x64-JIT-Compiler ist bekanntermaßen aggressiver in seinen Optimierungen als der x86. (Unter „ Array Bounds Check Elimination in der CLR “ finden Sie einen Fall, in dem die x86- und x64-Compiler semantisch unterschiedlichen Code generieren.)
In diesem Fall erkennt der x64-Compiler, dass er
x
nie gelesen wird, und beseitigt seine Zuweisung vollständig. Dies wird bei der Compileroptimierung als Dead Code Elimination bezeichnet . Um dies zu verhindern, fügen Sie direkt nach der Zuweisung die folgende Zeile hinzu:Sie werden feststellen, dass nicht nur der korrekte Wert von
3
get gedruckt wird, sondern dass die Variable nach dem Aufruf, der darauf verweist, auchx
den korrekten Wert im Debugger anzeigt (bearbeiten)Console.WriteLine
.Bearbeiten : Christopher Currens bietet eine alternative Erklärung, die auf einen Fehler in Visual Studio 2010 hinweist, der möglicherweise genauer ist als der oben genannte.
quelle
Console.WriteLine()
.Console.WriteLine
.x
Variable überhaupt nicht auflösen . Es würde nicht im Fenster der Einheimischen angezeigt, und wenn Sie versuchen, es in der Uhr oder in den unmittelbaren Fenstern anzuzeigen, wird die folgende Meldung angezeigt :The name 'x' does not exist in the current context
.