Der folgende Beispielcode ist natürlich vorgekommen. Plötzlich war mein Code eine sehr böse klingende FatalExecutionEngineError
Ausnahme. Ich habe gut 30 Minuten damit verbracht, die Täterprobe zu isolieren und zu minimieren. Kompilieren Sie dies mit Visual Studio 2012 als Konsolen-App:
class A<T>
{
static A() { }
public A() { string.Format("{0}", string.Empty); }
}
class B
{
static void Main() { new A<object>(); }
}
Sollte diesen Fehler in .NET Framework 4 und 4.5 erzeugen:
Ist dies ein bekannter Fehler, was ist die Ursache und was kann ich tun, um ihn zu mindern? Meine derzeitige Arbeit besteht darin, sie nicht zu verwenden string.Empty
, aber belle ich den falschen Baum an? Wenn Sie etwas an diesem Code A
ändern, funktioniert er wie erwartet - zum Beispiel entfernen Sie den leeren statischen Konstruktor von oder ändern Sie den Typparameter von object
nach int
.
Ich habe diesen Code auf meinem Laptop ausprobiert und er hat sich nicht beschwert. Ich habe jedoch meine Haupt-App ausprobiert und sie stürzte auch auf dem Laptop ab. Ich muss etwas verstümmelt haben, als ich das Problem reduziert habe. Ich werde sehen, ob ich herausfinden kann, was das war.
Mein Laptop stürzte mit dem gleichen Code wie oben ab, mit Framework 4.0, aber Main stürzte sogar mit 4.5 ab. Beide Systeme verwenden VS'12 mit den neuesten Updates (Juli?).
Weitere Informationen :
- IL-Code (kompiliertes Debug / Beliebige CPU / 4.0 / VS2010 (sollte diese IDE keine Rolle spielen?)): Http://codepad.org/boZDd98E
- Nicht gesehen VS 2010 mit 4.0. Nicht mit / ohne Optimierungen abstürzen, andere Ziel-CPU, Debugger angeschlossen / nicht angeschlossen, etc. - Tim Medora
- Abstürze im Jahr 2010, wenn ich AnyCPU verwende, sind in x86 in Ordnung. Abstürze in Visual Studio 2010 SP1 mit Platform Target = AnyCPU, aber mit Platform Target = x86 in Ordnung. Auf dieser Maschine ist auch VS2012RC installiert, sodass 4.5 möglicherweise einen direkten Austausch durchführt. Verwenden Sie AnyCPU und TargetPlatform = 3.5, dann stürzt es nicht ab und sieht aus wie eine Regression im Framework.- colinsmith
- Kann auf x86, x64 oder AnyCPU in VS2010 mit 4.0 nicht reproduziert werden. - - Fuji
- Kommt nur für x64 vor (2012rc, Fx4.5) - Henk Holterman
- VS2012 RC auf Win8 RP. Dieses MDA wird beim Targeting von .NET 4.5 zunächst nicht angezeigt. Bei der Umstellung auf .NET 4.0 wurde der MDA angezeigt. Nach dem Zurückschalten auf .NET 4.5 bleibt der MDA erhalten. - Wayne
Antworten:
Dies ist auch keine vollständige Antwort, aber ich habe ein paar Ideen.Ich glaube, ich habe eine so gute Erklärung gefunden, wie wir sie finden werden, ohne dass jemand vom .NET JIT-Team antwortet.
AKTUALISIEREN
Ich habe etwas tiefer geschaut und glaube, die Ursache des Problems gefunden zu haben. Dies scheint auf eine Kombination aus einem Fehler in der JIT-Typinitialisierungslogik und einer Änderung im C # -Compiler zurückzuführen zu sein, die auf der Annahme beruht, dass die JIT wie beabsichtigt funktioniert. Ich denke, der JIT-Fehler war in .NET 4.0 vorhanden, wurde aber durch die Änderung im Compiler für .NET 4.5 aufgedeckt.
Ich denke nicht, dass dies
beforefieldinit
hier das einzige Problem ist. Ich denke es ist einfacher als das.Der Typ
System.String
in mscorlib.dll aus .NET 4.0 enthält einen statischen Konstruktor:In der .NET 4.5-Version von mscorlib.dll
String.cctor
fehlt (der statische Konstruktor) auffällig:In beiden Versionen ist der
String
Typ geschmückt mitbeforefieldinit
:Ich habe versucht, einen Typ zu erstellen, der ähnlich wie IL kompiliert wird (so dass er statische Felder, aber keinen statischen Konstruktor enthält
.cctor
), aber ich konnte es nicht tun. Alle diese Typen haben eine.cctor
Methode in IL:Ich vermute, dass sich zwischen .NET 4.0 und 4.5 zwei Dinge geändert haben:
Erstens: Der EE wurde so geändert, dass er automatisch initialisiert wird
String.Empty
aus nicht verwaltetem Code . Diese Änderung wurde wahrscheinlich für .NET 4.0 vorgenommen.Zweitens: Der Compiler wurde so geändert, dass er keinen statischen Konstruktor für Zeichenfolgen ausgibt, da er das weiß
String.Empty
dieser von der nicht verwalteten Seite zugewiesen wird. Diese Änderung wurde anscheinend für .NET 4.5 vorgenommen.Es scheint , dass der EE nicht nicht zuordnen
String.Empty
bald genug entlang einiger Optimierungsweg. Die am Compiler vorgenommene Änderung (oder was auch immer geändert wurde, um sieString.cctor
verschwinden zu lassen ) erwartete, dass der EE diese Zuweisung vorString.Empty
der Ausführung eines Benutzercodes vornimmt, aber es scheint, dass der EE diese Zuweisung nicht vornimmt, bevor sie in Methoden mit reifizierten generischen Klassen vom Referenztyp verwendet wird.Schließlich glaube ich, dass der Fehler auf ein tieferes Problem in der JIT-Typinitialisierungslogik hinweist. Es scheint, dass die Änderung im Compiler ein Sonderfall ist
System.String
, aber ich bezweifle, dass die JIT hier einen Sonderfall gemacht hatSystem.String
.Original
Zunächst einmal WOW Die BCL-Mitarbeiter sind mit einigen Leistungsoptimierungen sehr kreativ geworden. Viele der
String
Methoden werden jetzt mit einem statisch zwischengespeicherten Thread-StringBuilder
Objekt ausgeführt.Ich bin diesem Beispiel eine Weile gefolgt, werde aber
StringBuilder
auf dem nicht verwendetTrim
Codepfad verwendet, daher habe ich beschlossen, dass es sich nicht um ein statisches Thread-Problem handeln kann.Ich glaube, ich habe eine seltsame Manifestation des gleichen Fehlers gefunden.
Dieser Code schlägt mit einer Zugriffsverletzung fehl:
Wenn jedoch uncomment Sie
//new A<int>(out s);
inMain
dann funktioniert der Code nur in Ordnung. WennA
es mit einem Referenztyp geändert wird, schlägt das Programm tatsächlich fehl. WennA
es jedoch mit einem Werttyp geändert wird, schlägt der Code nicht fehl. Auch wenn Sie denA
statischen Konstruktor auskommentieren, schlägt der Code nie fehl. Nach dem Eingraben inTrim
undFormat
ist klar, dass das Problem darin besteht, dassLength
es inline ist und dass in diesen obigen Beispielen derString
Typ nicht initialisiert wurde. Insbesondere wird innerhalb des Körpers desA
Konstruktorsstring.Empty
nicht korrekt zugewiesen, obwohl innerhalb des Körpers vonMain
,string.Empty
ist.Es ist für mich erstaunlich, dass die Typinitialisierung von
String
irgendwie davon abhängt, ob sieA
mit einem Werttyp bestätigt wird oder nicht . Meine einzige Theorie ist, dass es einen optimierenden JIT-Codepfad für die generische Typinitialisierung gibt, der von allen Typen gemeinsam genutzt wird, und dass dieser Pfad Annahmen über BCL-Referenztypen ("spezielle Typen?") Und deren Status macht. Ein kurzer Blick durch andere BCL-Klassen mitpublic static
Feldern zeigt, dass im Grunde alle einen statischen Konstruktor implementieren (auch solche mit leeren Konstruktoren und ohne Daten wieSystem.DBNull
undSystem.Empty
. BCL-Werttypen mitpublic static
Feldern scheinen keinen statischen Konstruktor zu implementieren (System.IntPtr
zum Beispiel) Dies scheint darauf hinzudeuten, dass die JIT einige Annahmen zur Initialisierung des BCL-Referenztyps trifft.Zu Ihrer Information Hier ist der JITed-Code für die beiden Versionen:
A<object>.ctor(out string)
::A<int32>.ctor(out string)
::Der Rest des Codes (
Main
) ist zwischen den beiden Versionen identisch.BEARBEITEN
Darüber hinaus ist die IL aus den beiden Versionen identisch, mit Ausnahme des Aufrufs von
A.ctor
inB.Main()
, in dem die IL für die erste Version Folgendes enthält:gegen
in dieser Sekunde.
Eine andere zu beachtende Sache ist, dass der JITed-Code für
A<int>.ctor(out string)
: der gleiche ist wie in der nicht generischen Version.quelle
string.Empty
oder""
... :)typeof(string).GetField("Empty").SetValue(null, "Hello world!"); Console.WriteLine(string.Empty);
unter .NET 4.0 andere Ergebnisse liefert als .NET 4.5. Bezieht sich diese Änderung auf die oben beschriebene Änderung? Wie kann .NET 4.5 mich technisch ignorieren, wenn ich einen Feldwert ändere? Vielleicht sollte ich eine neue Frage dazu stellen?Ich vermute sehr, dass dies durch diese Optimierung (im Zusammenhang mit
BeforeFieldInit
) in .NET 4.0 verursacht wird.Wenn ich mich richtig erinnere:
Wenn Sie einen statischen Konstruktor explizit deklarieren,
beforefieldinit
wird ausgegeben, um der Laufzeit mitzuteilen, dass der statische Konstruktor ausgeführt werden muss, bevor statische Mitglieder darauf zugreifen können .Meine Vermutung:
Ich würde vermuten , dass sie irgendwie diese Tatsache auf den x64 JITer geschraubt, so dass , wenn eine andere der Art statisches Element aus einer Klasse zugegriffen wird , deren eigene statische Konstruktor hat bereits ausgeführt, ist es irgendwie überspringt läuft (oder führt in der falschen Reihenfolge) die statischer Konstruktor - und verursacht daher einen Absturz. (Sie erhalten wahrscheinlich keine Nullzeigerausnahme weil sie nicht nullinitialisiert ist.)
Ich habe Ihren Code nicht ausgeführt, daher ist dieser Teil möglicherweise falsch. Wenn ich jedoch eine andere Vermutung anstellen müsste, würde ich sagen, dass möglicherweise etwas
string.Format
(oderConsole.WriteLine
, was ähnlich ist) intern zugegriffen werden muss, das den Absturz verursacht, z Vielleicht eine Gebietsschema- bezogene Klasse, die eine explizite statische Konstruktion benötigt.Auch hier habe ich es nicht getestet, aber es ist meine beste Vermutung bei den Daten.
Fühlen Sie sich frei, meine Hypothese zu testen und mich wissen zu lassen, wie es geht.
quelle
B
kein statischer Konstruktor vorhanden ist, und er tritt nicht auf, wenn erA
mit einem Werttyp geändert wird. Ich finde es etwas komplizierter.B
Ein statischer Konstruktor spielt keine Rolle. DaA
es einen statischen Ctor hat, bringt die Laufzeit die Reihenfolge, in der sie ausgeführt wird, durcheinander, wenn sie mit einer Gebietsschema-bezogenen Klasse in einem anderen Namespace verglichen wird. Dieses Feld ist also noch nicht initialisiert. Wenn Sie jedochA
mit einem Werttyp instanziieren , ist dies möglicherweise der zweite Durchlauf der Laufzeit durch die InstanziierungA
(die CLR hat sie wahrscheinlich bereits mit einem Referenztyp als Optimierung vorinstanziiert), sodass die Reihenfolge funktioniert, wenn sie ein zweites Mal ausgeführt wird .beforefieldinit
Optimierung die Hauptursache ist. Es kann sein, dass sich einige der tatsächlichen Erklärungen von den von mir erwähnten unterscheiden, aber die Grundursache ist wahrscheinlich dieselbe.A<object>.ctor()
.Eine Beobachtung, aber DotPeek zeigt den dekompilierten String. Leere also:
Wenn ich meine eigene auf
Empty
die gleiche Weise deklariere , außer ohne das Attribut, erhalte ich den MDA nicht mehr:quelle
""
Lösungen.