更新:完全重写答案。原始答案包含通过分而治之在任何系统上找到最大可能可寻址数组的方法,如果您有兴趣,请参阅此答案的历史。新答案试图解释 56 字节的差距。
在他自己的回答中,AZ 解释说最大数组大小限制为小于 2GB 上限,并且通过一些试验和错误(或其他方法?)发现以下(总结):
- 如果类型的大小为 1、2、4 或 8 字节,则最大可占用大小为 2GB - 56 字节;
- 如果类型的大小为 16 字节,则最大值为 2GB - 48 字节;
- 如果类型的大小为 32 字节,则最大值为 2GB - 32 字节。
我不完全确定 16 字节和 32 字节的情况。如果数组是结构数组或内置类型,则数组的总可用大小可能会有所不同。我将强调 1-8 字节的类型大小(我也不确定,见结论)。
数组的数据布局
要理解为什么 CLR 不允许精确的2GB / IntPtr.Size
元素,我们需要知道数组的结构。一个很好的起点是这篇SO article,但不幸的是,有些信息似乎是错误的,或者至少是不完整的。这篇关于 .NET CLR 如何创建运行时对象的深入文章以及CodeProject 上的这篇Arrays Undocumented文章被证明是无价的。
综合这些文章中的所有信息,可以归结为 32 位系统中的阵列的以下布局:
单维,内置型
SSSSTTTLLLL[...数据...]0000
^ 同步块
^ 型手柄
^ 长度数组
^ 空
每个部分DWORD
在大小上都是一个系统。在 64 位窗口上,如下所示:
单维,内置型
SSSSSSSSTTTTTTTTLLLLLLL[...数据...]00000000
^ 同步块
^ 型手柄
^ 长度数组
^ 空
当它是一个对象数组(即字符串、类实例)时,布局看起来略有不同。如您所见,添加了数组中对象的类型句柄。
单维,内置型
SSSSSSSSTTTTTTTTLLLLLLLtttttttt[...数据...]00000000
^ 同步块
^ 型手柄
^ 长度数组
^ 类型句柄数组元素类型
^ 空
进一步看,我们发现一个内置类型,或者实际上,任何结构类型,都有自己特定的类型处理程序(都uint
共享相同的,但是int
a 对数组有不同的类型处理程序然后 auint
或byte
)。所有对象数组共享相同的类型处理程序,但有一个额外的字段指向对象的类型处理程序。
关于结构类型的注意事项:可能并不总是应用填充,这可能会导致难以预测结构的实际大小。
仍然不是 56 字节...
要计入 AZ 答案的 56 个字节,我必须做出一些假设。我假设:
- 同步块和类型句柄计入对象的大小;
- 保存数组引用(对象指针)的变量计入对象的大小;
- 数组的空终止符计入对象的大小。
一个同步块放置在变量指向的地址之前,这使它看起来不是对象的一部分。但事实上,我相信它是并且它计入内部 2GB 限制。添加所有这些,我们得到,对于 64 位系统:
ObjectRef +
Syncblock +
Typehandle +
Length +
Null pointer +
--------------
40 (5 * 8 bytes)
还没到56。也许有人可以在调试期间查看内存视图,以检查数组的布局在 64 位窗口下的样子。
我的猜测是沿着这些思路(选择,混合和匹配):
2GB 永远不可能,因为这是下一段的一个字节。最大的块应该是2GB - sizeof(int)
. 但这很愚蠢,因为 mem 索引应该从零开始,而不是从一开始;
任何大于 85016 字节的对象都将放在 LOH(大对象堆)上。这可能包括一个额外的指针,甚至是一个包含 LOH 信息的 16 字节结构。也许这算作极限;
对齐:假设 objectref 不计数(无论如何它在另一个 mem 段中),总间隙为 32 个字节。系统很可能更喜欢 32 字节边界。重新审视内存布局。如果起始点需要位于 32 字节边界上,并且它需要为之前的同步块留出空间,则同步块将在前 32 字节块的末尾结束。像这样的东西:
XXXXXXXXXXXXXXXXXXXXXXXXSSSSSSSSTTTTTTTTLLLLLLLLtttttttt[...data...]00000000
其中XXX..
代表跳过的字节。
多维数组:如果您Array.CreateInstance
使用 1 维或更多维动态创建数组,则将使用两个额外的 DWORDS 创建单个暗数组,其中包含维度的大小和下限(即使您只有一个维度,但前提是下限被指定为非零)。我发现这不太可能,因为如果您的代码中出现这种情况,您可能会提到这一点。但这会使总开销达到 56 个字节;)。
结论
从我在这个小研究中收集到的所有信息中,我认为这Overhead + Aligning - Objectref
是最有可能和最合适的结论。然而,一位“真正的”CLR 大师或许能够对这个特殊的主题提供一些额外的启示。
这些结论都没有解释为什么 16 或 32 字节数据类型分别具有 48 和 32 字节间隙。
感谢一个具有挑战性的主题,在我的过程中学到了一些东西。当一些人发现这个新答案与问题更相关时,也许有些人可以取消投票(我最初误解了这一点,并为这可能造成的混乱道歉)。