我正在构建一个使用相对较大的表来完成工作的应用程序(准确地说是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 可能有一定的意义,但即便如此,我使用的表对于 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
s 或string
s 代替Tuple<int>
s 不会导致崩溃,“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)
这表明抖动确实会因试图抖动此方法的堆栈溢出而崩溃。尽管如此,它确实很奇怪,特别是我得到了一个例外。