以下示例代码自然发生.突然间,我的代码成了一个非常令人讨厌的FatalExecutionEngineError异常.我花了30分钟试图隔离并最小化罪魁祸首样本.使用Visual Studio 2012作为控制台应用程序对此进行编译:
class A<T>
{
static A() { }
public A() { string.Format("{0}", string.Empty); }
}
class B
{
static void Main() { new A<object>(); }
}
Run Code Online (Sandbox Code Playgroud)
应该在.NET framework 4和4.5上产生此错误:

这是一个已知的错误,原因是什么,我可以做些什么来减轻它?我目前的工作是不使用string.Empty,但我是在吠叫错误的树吗?更改有关该代码的任何内容使其按预期运行 - 例如删除空的静态构造函数A,或将类型参数更改object为int.
我在笔记本电脑上尝试了这个代码并没有抱怨.但是,我确实尝试了我的主应用程序,它也在笔记本电脑上崩溃了.在减少问题的时候,我必须把一些东西弄错了,我会看看能否弄清楚那是什么.
我的笔记本电脑使用与上面相同的代码崩溃,使用框架4.0,但主要崩溃,即使是4.5.两个系统都使用VS'12和最新更新(7月?).
更多信息:
Mic*_*zyk 114
这也不是一个完整的答案,但我有一些想法.
我相信我已经找到了一个很好的解释,因为我们会发现没有.NET JIT团队的人回答.
UPDATE
我看得更深一点,我相信我找到了问题的根源.它似乎是由JIT类型初始化逻辑中的错误和C#编译器中的更改引起的,该更改依赖于JIT按预期工作的假设.我认为JIT错误存在于.NET 4.0中,但是.NET 4.5的编译器发生了变化.
我不认为这beforefieldinit是唯一的问题.我认为这比那更简单.
System.String.NET 4.0中的mscorlib.dll中的类型包含一个静态构造函数:
.method private hidebysig specialname rtspecialname static
void .cctor() cil managed
{
// Code size 11 (0xb)
.maxstack 8
IL_0000: ldstr ""
IL_0005: stsfld string System.String::Empty
IL_000a: ret
} // end of method String::.cctor
Run Code Online (Sandbox Code Playgroud)
在.NET 4.5版本的mscorlib.dll中,String.cctor(静态构造函数)显然不存在:
.....没有静态构造函数:( .....
在这两个版本中,String类型都装饰有beforefieldinit:
.class public auto ansi serializable sealed beforefieldinit System.String
Run Code Online (Sandbox Code Playgroud)
我试图创建一个类似的类型,以便编译为IL(因此它有静态字段,但没有静态构造函数.cctor),但我无法做到.所有这些类型.cctor在IL中都有一个方法:
public class MyString1 {
public static MyString1 Empty = new MyString1();
}
public class MyString2 {
public static MyString2 Empty = new MyString2();
static MyString2() {}
}
public class MyString3 {
public static MyString3 Empty;
static MyString3() { Empty = new MyString3(); }
}
Run Code Online (Sandbox Code Playgroud)
我的猜测是.NET 4.0和4.5之间发生了两件事:
第一:更改了EE,以便String.Empty从非托管代码自动初始化.这种变化可能是针对.NET 4.0进行的.
第二:编译器已更改,因此它不会为字符串发出静态构造函数,因为知道String.Empty将从非托管端分配.这种变化似乎是针对.NET 4.5进行的.
EE 似乎不会String.Empty很快分配一些优化路径.对编译器所做的更改(或任何更改为make的更改String.cctor)预计EE会在执行任何用户代码之前进行此分配,但看起来EE之前String.Empty未在引用类型实现的通用类的方法中使用此赋值.
最后,我认为该错误表明JIT类型初始化逻辑中存在更深层次的问题.看来编译器的变化是一个特殊情况System.String,但我怀疑JIT在这里做了一个特例System.String.
原版的
首先,WOW BCL人员在进行一些性能优化方面非常有创意. 很多的String方法,现在使用线程静态缓存的执行StringBuilder对象.
我跟着那个引导了一段时间,但StringBuilder没有在Trim代码路径上使用,所以我认为它不能是一个Thread静态问题.
我想我发现了同一个bug的一个奇怪的表现.
此代码因访问冲突而失败:
class A<T>
{
static A() { }
public A(out string s) {
s = string.Empty;
}
}
class B
{
static void Main() {
string s;
new A<object>(out s);
//new A<int>(out s);
System.Console.WriteLine(s.Length);
}
}
Run Code Online (Sandbox Code Playgroud)
但是,如果你取消注释//new A<int>(out s);,Main那么代码就可以了.实际上,如果A使用任何引用类型进行了修改,程序将失败,但如果A使用任何值类型进行了修改,则代码不会失败.另外,如果你注释掉A静态构造函数,代码永远不会失败.挖掘到后Trim和Format,很明显的是,问题是,Length被内联的,并且这些样本在上述在String式尚未被初始化.特别是,在A构造函数体内,string.Empty未正确分配,尽管在体内Main,string.Empty正确分配.
令我惊讶的是,类型初始化以String某种方式取决于是否A使用值类型进行了实现.我唯一的理论是,在所有类型之间共享通用类型初始化的优化JIT代码路径,并且该路径对BCL引用类型("特殊类型?")及其状态进行假设.就让我们来看看,虽然其他BCL类与public static字段显示,基本上所有的人实现一个静态构造函数(即使是空的构造函数和任何数据,如System.DBNull和System.Empty.BCL值类型的public static字段似乎没有实现静态构造函数(System.IntPtr例如)这似乎表明JIT对BCL引用类型初始化做了一些假设.
仅供参考这是两个版本的JITed代码:
A<object>.ctor(out string):
public A(out string s) {
00000000 push rbx
00000001 sub rsp,20h
00000005 mov rbx,rdx
00000008 lea rdx,[FFEE38D0h]
0000000f mov rcx,qword ptr [rcx]
00000012 call 000000005F7AB4A0
s = string.Empty;
00000017 mov rdx,qword ptr [FFEE38D0h]
0000001e mov rcx,rbx
00000021 call 000000005F661180
00000026 nop
00000027 add rsp,20h
0000002b pop rbx
0000002c ret
}
Run Code Online (Sandbox Code Playgroud)
A<int32>.ctor(out string):
public A(out string s) {
00000000 sub rsp,28h
00000004 mov rax,rdx
s = string.Empty;
00000007 mov rdx,12353250h
00000011 mov rdx,qword ptr [rdx]
00000014 mov rcx,rax
00000017 call 000000005F691160
0000001c nop
0000001d add rsp,28h
00000021 ret
}
Run Code Online (Sandbox Code Playgroud)
其余代码(Main)在两个版本之间是相同的.
编辑
此外,除了调用A.ctorin 之外,两个版本的IL是相同的B.Main(),其中第一个版本的IL包含:
newobj instance void class A`1<object>::.ctor(string&)
Run Code Online (Sandbox Code Playgroud)
与
... A`1<int32>...
Run Code Online (Sandbox Code Playgroud)
在第二.
另一件需要注意的是:JITed代码A<int>.ctor(out string):与非泛型版本相同.