更新:回答は完全に書き直されました。元の回答には、分割統治によって任意のシステムで可能な最大のアドレス可能な配列を見つける方法が含まれていました。興味がある場合は、この回答の履歴を参照してください。新しい回答は、56 バイトのギャップを説明しようとしています。
彼自身の回答で、AZ は、最大配列サイズが 2 GB の上限よりも小さく制限されており、いくつかの試行錯誤 (または別の方法?) を使用して、次の (要約) を見つけると説明しました。
- 型のサイズが 1、2、4、または 8 バイトの場合、占有可能な最大サイズは 2GB - 56 バイトです。
- 型のサイズが 16 バイトの場合、最大は 2GB - 48 バイトです。
- タイプのサイズが 32 バイトの場合、最大は 2GB - 32 バイトです。
16 バイトと 32 バイトの状況については、よくわかりません。構造体の配列または組み込み型の場合、配列の使用可能な合計サイズは異なる場合があります。1 ~ 8 バイトの型サイズを強調します (これについてもよくわかりません。結論を参照してください)。
配列のデータ レイアウト
CLR が正確な要素を許可しない理由を理解するに2GB / IntPtr.Size
は、配列がどのように構造化されているかを知る必要があります。良い出発点はこのSO 記事ですが、残念ながら、一部の情報は間違っているか、少なくとも不完全なようです。.NET CLR がランタイム オブジェクトを作成する方法に関するこの詳細な記事と、CodeProject に関するこの文書化されていない配列の記事は非常に貴重であることが証明されました。
これらの記事のすべての情報を考慮すると、32 ビット システムの配列の次のレイアウトになります。
一次元ビルトインタイプ
SSSSTTTTLLL[...データ...]0000
^ 同期ブロック
^ 型ハンドル
^ 長さ配列
^ヌル
各パーツDWORD
のサイズは 1 システムです。64 ビット Windows では、これは次のようになります。
一次元ビルトインタイプ
SSSSSSSSTTTTTTTTLLLLLLLLL[...データ...]00000000
^ 同期ブロック
^ 型ハンドル
^ 長さ配列
^ヌル
オブジェクトの配列 (つまり、文字列、クラス インスタンス) の場合、レイアウトは少し異なります。ご覧のとおり、配列内のオブジェクトへの型ハンドルが追加されています。
一次元ビルトインタイプ
SSSSSSSSTTTTTTTTLLLLLLLLLtttttttt[...データ...]00000000
^ 同期ブロック
^ 型ハンドル
^ 長さ配列
^ type ハンドル配列要素の型
^ヌル
さらに調べてみると、組み込み型、または実際には任意の構造体型が、独自の特定の型ハンドラーを取得することがわかります (すべてuint
同じものを共有しますが、int
は配列に対して異なる型ハンドラーを持ち、 auint
またはを持ちbyte
ます)。オブジェクトのすべての配列は同じ型ハンドラーを共有しますが、オブジェクトの型ハンドラーを指す追加のフィールドがあります。
構造体型に関する注意: パディングが常に適用されるとは限らないため、構造体の実際のサイズを予測するのが難しくなる場合があります。
まだ56バイトではありません...
AZ の回答の 56 バイトにカウントするには、いくつかの仮定を行う必要があります。私はそれを仮定します:
- syncblock とタイプ ハンドルは、オブジェクトのサイズにカウントされます。
- 配列参照 (オブジェクト ポインター) を保持する変数は、オブジェクトのサイズにカウントされます。
- 配列の null ターミネータは、オブジェクトのサイズにカウントされます。
変数が指し示すアドレスの前に syncblock が配置されるため、オブジェクトの一部ではないように見えます。しかし、実際にはそうであり、内部の 2GB 制限にカウントされます。これらをすべて追加すると、64 ビット システムでは次のようになります。
ObjectRef +
Syncblock +
Typehandle +
Length +
Null pointer +
--------------
40 (5 * 8 bytes)
まだ56ではありません。おそらく、誰かがデバッグ中にメモリ ビューを見て、配列のレイアウトが 64 ビット ウィンドウでどのように見えるかを確認できます。
私の推測では、これらの線に沿ったものです (選択して、組み合わせて一致させてください):
2GB は、次のセグメントへの 1 バイトであるため、決して可能ではありません。最大のブロックは2GB - sizeof(int)
. しかし、これはばかげています。メモリ インデックスは 1 ではなく 0 から開始する必要があるためです。
85016 バイトを超えるオブジェクトは、LOH (ラージ オブジェクト ヒープ) に配置されます。これには、追加のポインター、または LOH 情報を保持する 16 バイトの構造体が含まれる場合があります。おそらく、これは制限にカウントされます。
整列: objectref がカウントされないと仮定すると (別の mem セグメントにある)、合計ギャップは 32 バイトです。システムが 32 バイト境界を好む可能性は十分にあります。メモリ レイアウトを見直してください。開始点が 32 バイト境界上にある必要があり、その前に同期ブロック用のスペースが必要な場合、同期ブロックは最初の 32 バイト ブロックの終わりになります。このようなもの:
XXXXXXXXXXXXXXXXXXXXXXXXSSSSSSSSTTTTTTTTLLLLLLLLtttttttt[...data...]00000000
whereXXX..
はスキップされたバイトを表します。
多次元配列: Array.CreateInstance
1 つ以上の次元で配列を動的に作成する場合、次元のサイズと下限を含む 2 つの追加の DWORD を含む単一の次元配列が作成されます (次元が 1 つしかない場合でも、下限は非ゼロとして指定されます)。これがあなたのコードに当てはまる場合、おそらくこれについて言及したであろうため、これはほとんどありそうにないと思います。しかし、合計で 56 バイトのオーバーヘッドが発生します ;)。
結論
Overhead + Aligning - Objectref
この小さな調査で集めたすべてのことから、これが最も可能性が高く、最も適切な結論だと思います。ただし、「本物の」CLR の第一人者であれば、この独特なテーマにさらに光を当てることができるかもしれません。
これらの結論のいずれも、16 バイトまたは 32 バイトのデータ型にそれぞれ 48 バイトおよび 32 バイトのギャップがある理由を説明していません。
やりがいのあるテーマをありがとう、途中で何かを学びました。おそらく、この新しい回答が質問に関連していることがわかったときに、反対票を投じることができる人もいるでしょう(私が最初に誤解したものであり、これが原因で混乱が生じたことをお詫びします)。