.NET 数组的内存布局

Posted

技术标签:

【中文标题】.NET 数组的内存布局【英文标题】:Memory layout of a .NET array 【发布时间】:2010-10-03 23:38:58 【问题描述】:

是什么?

以这个数组为例:

Int32[] x = new Int32[10];

我了解数组的大部分是这样的:

0000111122223333444455556666777788889999

其中每个字符为一个字节,数字对应于数组中的索引。

另外,我知道有一个类型引用,所有对象都有一个syncblock-index,所以上面可以调整成这样:

ttttssss0000111122223333444455556666777788889999
        ^
        +- object reference points here

另外,需要存储数组的长度,所以也许这样更正确:

ttttssssllll0000111122223333444455556666777788889999
        ^
        +- object reference points here

这是完整的吗?数组中有更多数据吗?

我问的原因是我们试图估计一个相当大的数据语料库的几个不同的内存表示将占用多少内存,并且数组的大小变化很大,所以开销可能对一种解决方案有很大影响,但对另一种可能影响不大。

所以基本上,对于一个数组,有多少开销,这基本上是我的问题。

数组坏了小队醒来之前,这部分解决方案是静态构建一次引用-经常类型的东西,因此这里不需要使用可增长列表。

【问题讨论】:

请注意:同步块位于方法类型引用之前。该变量包含一个指向方法类型引用(上面的tttt)的指针(引用),跳过了同步块部分。对于没有特定维度或特定下限的数组,布局实际上看起来像 ssssttttllll000011...9999NULL 【参考方案1】:

检查这一点的一种方法是查看 WinDbg 中的代码。所以给定下面的代码,让我们看看它是如何出现在堆上的。

var numbers = new Int32[]  0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ;

首先要做的是定位实例。由于我在Main() 中将其设为本地,因此很容易找到实例的地址。

从地址我们可以转储实际的实例,这给了我们:

0:000> !do 0x0141ffc0
Name: System.Int32[]
MethodTable: 01309584
EEClass: 01309510
Size: 52(0x34) bytes
Array: Rank 1, Number of elements 10, Type Int32
Element Type: System.Int32
Fields:
None

这告诉我们这是我们的 Int32 数组,有 10 个元素,总大小为 52 字节。

让我们转储实例所在的内存。

0:000> d 0x0141ffc0
0141ffc0 [84 95 30 01 0a 00 00 00-00 00 00 00 01 00 00 00  ..0.............
0141ffd0  02 00 00 00 03 00 00 00-04 00 00 00 05 00 00 00  ................
0141ffe0  06 00 00 00 07 00 00 00-08 00 00 00 09 00 00 00  ................
0141fff0  00 00 00 00]a0 20 40 03-00 00 00 00 00 00 00 00  ..... @.........
01420000  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
01420010  10 6d 99 00 00 00 00 00-00 00 01 40 50 f7 3d 03  .m.........@P.=.
01420020  03 00 00 00 08 00 00 00-00 01 00 00 00 00 00 00  ................
01420030  1c 24 40 03 00 00 00 00-00 00 00 00 00 00 00 00  .$@.............

我为 52 个字节插入了括号。

前四个字节是对 01309584 处方法表的引用。 然后是数组长度的四个字节。 接下来是数字 0 到 9(每个四个字节)。 最后四个字节为空。我不完全确定,但我想如果实例用于锁定,那肯定是对同步块数组的引用的存储位置。

编辑:第一次发帖时忘记了长度。

列表有点不正确,因为正如 romkyns 指出的那样,实例实际上从地址 - 4 开始,第一个字段是 Syncblock。

【讨论】:

最后四个字节实际上在这个数组之外。这是因为你得到的指针在对象中偏移了 4;同步块索引首先出现在偏移量 -4 处。 reference "前四个字节是对 01309510 处方法表的引用。" - 不应该是“在 01309584”?【参考方案2】:

很好的问题。我找到了this 文章,其中包含值类型和引用类型的框图。另请参阅article,其中 Ritcher 声明:

[snip] 每个数组都有一些额外的 相关的开销信息 它。此信息包含排名 数组的(维数), 每个维度的下界 数组(几乎总是 0)和 每个维度的长度。开销 还包含每个元素的类型 在数组中。

【讨论】:

我建议在此解决方案中添加 Code Project 的“Arrays UNDOCUMENTED”文章:codeproject.com/KB/dotnet/arrays.aspx 该片段出自的书名为“CLR via C#”,是一本很棒的书。 非常同意 SnOrfus - 好书。【参考方案3】:

好问题!我想亲眼看看,似乎是试用 CorDbg.exe 的好机会……

似乎对于简单的整数数组,格式是:

ssssllll000011112222....nnnn0000

其中 s 是同步块,l 是数组的长度,然后是各个元素。好像最后有一个finally 0,不知道为什么。

对于多维数组:

ssssttttl1l1l2l2????????
    000011112222....nnnn000011112222....nnnn....000011112222....nnnn0000

其中 s 是同步块,t 是元素的总数,l1 是第一个维度的长度,l2 是第二个维度的长度,然后是两个零,然后依次是所有元素,最后又是一个零.

对象数组被当作整型数组,这次是引用的内容。锯齿状数组是对象数组,其中引用指向其他数组。

【讨论】:

【参考方案4】:

一个数组对象必须存储它有多少维以及每个维的长度。所以至少还有一个数据元素要添加到您的模型中

【讨论】:

以上是关于.NET 数组的内存布局的主要内容,如果未能解决你的问题,请参考以下文章

[.NET] 结构体布局详解与结构体内存对齐具体方式

[.NET] 结构体布局详解与结构体内存对齐具体方式

具有原始类型的单个数组成员的标准布局结构的保证内存布局

Solidity - 内存布局

new对象数组时的内存布局

使用反射确定 .Net 类型在内存中的布局方式