比較的大きなテーブルを使用して作業を行うアプリケーションを構築しています (正確には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# コンパイラは問題なくコンパイルします。テーブル生成アプリケーションも問題なく動作します。実際、Release モードに切り替えたところ、アプリケーションが起動しました。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 ファイルの最初のプロジェクトを実行し、次に 2 番目のプログラムを実行して StackOverflowException を取得します。上記は私のコンピューターでクラッシュすることに注意してください。異なるプラットフォームなどではそうではないかもしれません。そうでない場合は、300000 を増やしてみてください。
私のプロジェクトはリリース モードでクラッシュしないため、デバッグ モードの代わりにリリース モードを使用すると、制限がわずかに増加するようです。ただし、上記のコードは両方のモードでクラッシュします。
リテラルint
s またはstring
s の代わりにTuple<int>
s を使用してもクラッシュは発生せず、「new int()」も発生しません (ただし、リテラル 0 に変換される可能性があります)。単一のint
フィールドで構造体を使用すると、クラッシュが発生します。コンストラクターを初期化子として使用することに関連しているようです。
私の推測では、コレクション初期化子が何らかの方法で再帰的に実装されているため、スタック オーバーフローが説明されます。ただし、反復ソリューションははるかに単純で効率的であるように見えるため、これは非常に奇妙なことです。C# コンパイラ自体は、プログラムに問題はなく、非常に高速にコンパイルされます (より大きなコレクションもうまく処理しますが、予想どおり、明らかに巨大なコレクションでクラッシュします)。
おそらくテーブルをバイナリ ファイルに直接書き込んで、そのファイルをリンクする方法があると思いますが、まだ見ていません。
2 つの質問があると思います。なぜ上記のようなことが起こるのか、どうすれば回避できるのでしょうか。
編集: .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 しようとするとスタック オーバーフローが発生し、実際にジッターがクラッシュすることを示唆しています。それでも、そうするのは奇妙です。特に、例外が発生するのは奇妙です。