我正在开发一个应用程序,它使用相对较大的表格来完成工作(确切地说是LR表格)。由于我已经在生成代码,而且表格并不是那么大,所以我决定通过生成使用C#集合初始化语法在我的生成程序启动时初始化表格的代码来序列化我的表格:
public static readonly int[,] gotoTable = new int[,]
{
{
0,1,0,0,0,0,0,0,0,0,0,0,0,0,(...)
},
{
0,0,4,0,5,6,0,0,0,0,0,7,0,0,(...)
},
(...)
奇怪的是,当我生成只有几十万条记录的表格时,所生成的应用程序在启动时会崩溃并出现StackOverflowException。C#编译器可以正常编译它,表格生成应用程序也可以正常运行。事实上,当我切换到发布模式时,该应用程序确实启动了。虽然OutOfMemoryException可能会有些道理,但即使是这样,我使用的表格也太小了。
复制此代码以重现问题:
警告:尝试在发布模式下运行以下代码会导致Visual Studio 2010崩溃,请小心保存未保存的工作。此外,如果您生成的代码存在大量编译错误,Visual Studio也会挂起。
//Generation Project, main.cs:
using (StreamWriter writer = new StreamWriter("../../../VictimProject/Tables.cs"))
{
writer.WriteLine("using System;");
writer.WriteLine("public static class Tables");
writer.WriteLine("{");
writer.WriteLine(" public static readonly Tuple<int>[] bigArray = new Tuple<int>[]");
writer.WriteLine(" {");
for (int i = 0; i < 300000; i++)
writer.WriteLine(" new Tuple<int>(" + i + "),");
writer.WriteLine(" };");
writer.WriteLine("}");
}
//Victim Project, main.cs:
for (int i = 0; i < 1234; i++)
{
// Preventing the jitter from removing Tables.bigArray
if (Tables.bigArray[i].Item1 == 10)
Console.WriteLine("Found it!");
}
Console.ReadKey(true);
运行Tables.cs文件的第一个项目,然后运行第二个程序以获取StackOverflowException。请注意,上述代码在我的计算机上会崩溃:在不同平台上可能不会崩溃;如果出现问题,请尝试将300000增加。
使用发布模式而不是调试模式似乎略微增加了限制,因为我的项目在发布模式下不会崩溃。然而,上面的代码在两种模式下都会崩溃。
使用字面值int或string代替Tuple不会导致崩溃,"new int()"也不会崩溃(但可能会转换为文本0)。使用只包含一个int字段的结构体会导致崩溃。似乎与使用初始化器的构造函数有关。
我猜想集合初始化器以递归方式实现,这可以解释堆栈溢出。但是,作为迭代解决方案,这似乎很奇怪且效率较低。C#编译器本身没有任何问题,它编译得非常快(它甚至处理更大的集合很好,但是当集合非常大时确实会崩溃)。
我猜我可能有一种方法可以直接将表格写入二进制文件,然后链接该文件,但我还没有研究过。
我想我有两个问题:为什么会出现上述情况,以及如何解决它?
编辑:反汇编.exe文件后,发现一些有趣的细节。
.maxstack 4
.locals init ([0] class [mscorlib]System.Tuple`1<int32>[] CS$0$0000)
IL_0000: ldc.i4 0x493e0
IL_0005: newarr class [mscorlib]System.Tuple`1<int32>
IL_000a: stloc.0
IL_000b: ldloc.0
IL_000c: ldc.i4.0
IL_000d: ldc.i4.0
IL_000e: newobj instance void class [mscorlib]System.Tuple`1<int32>::.ctor(!0)
IL_0013: stelem.ref
IL_0014: ldloc.0
IL_0015: ldc.i4.1
IL_0016: ldc.i4.1
IL_0017: newobj instance void class [mscorlib]System.Tuple`1<int32>::.ctor(!0)
IL_001c: stelem.ref
(goes on and on)
这表明,当尝试JIT此方法时,抖动确实会因堆栈溢出而崩溃。但奇怪的是它确实发生了,并且特别是我获得了异常。
class Program { static void Main() { Main(); } }
时,你认为哪个堆栈会溢出?这不是IL评估堆栈 - 它纯粹是虚拟的。 - phoog