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