32位与64位CLR对象大小的理解

16
我正在尝试理解32位和64位处理器之间的对象大小差异。假设我有一个简单的类。
class MyClass   
{  
    int x;  
    int y;  
}  
在32位机器上,整数的大小为4个字节。如果将Syncblock(另外4个字节)添加到其中,则对象大小将为12个字节。为什么它显示为16个字节?
0:000> !do 0x029d8b98  
Name: ConsoleApplication1.Program+MyClass  
MethodTable: 000e33b0  
EEClass: 000e149c  
Size: 16(0x10) bytes  
(C:\MyTemp\ConsoleApplication1\ConsoleApplication1\bin\x86\Debug\ConsoleApplication1.exe)  
Fields:  
      MT    Field   Offset                 Type VT     Attr    Value Name  
71972d70  4000003        4         System.Int32  1 instance        0 x  
71972d70  4000004        8         System.Int32  1 instance        0 y  
在64位机器上,整数仍然是4个字节,唯一改变的是Syncblock将成为8个字节(因为在64位机器上指针是8个字节)。这意味着对象大小将为16个字节。为什么它显示为24个字节?
0:000> !do 0x00000000028f3c90  
Name: ConsoleApplication1.Program+MyClass  
MethodTable: 000007ff00043af8  
EEClass: 000007ff00182408  
Size: 24(0x18) bytes  
(C:\MyTemp\ConsoleApplication1\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe)  
Fields:  
      MT            Field   Offset                 Type VT     Attr            Value Name  
000007fef4edd998  4000003        8         System.Int32  1 instance                0 x  
000007fef4edd998  4000004        c         System.Int32  1 instance                0 y  
4个回答

26

CLR可以自由地在内存中布置对象,这是一种实现细节。您不应该依赖于任何特定的布局。

您看到的差异是由于缺少TypeHandle字段,该字段也是CLR对象头的一部分。此外,字段可能会对齐到字节边界。


来自高级 .Net 调试 - CLR 对象的内部结构:

一个对象的CLR内部结构是:

[DWORD: SyncBlock][DWORD: MethodTable 指针][DWORD: 引用类型指针]…[值类型字段的值]…

对象头: [DWORD: SyncBlock]
对象指针: [DWORD: MethodTable 指针][DWORD: 引用类型指针]…[值类型字段的值]…

每个对象都有一个 ObjHeader(在负偏移处)。ObjHeader具有指向SyncBlock的索引。


因此,您的对象可能是这样布局的:

x86:(对齐到8字节)

  Syncblk     TypeHandle       X            Y
------------,------------|------------,------------|
                         8                         16

x64:(对齐到8字节)

         Syncblk                  TypeHandle             X            Y
-------------------------|-------------------------|------------,------------|
                         8                         16                        24

参见:深入了解.NET Framework内部,了解CLR如何创建运行时对象

9
同步块位于对象指针的负偏移量处。在偏移量为0的第一个字段是方法表指针,在x64上为8字节。因此,在x86上,它是SB + MT + X + Y = 4 + 4 + 4 + 4 = 16字节。同步块索引在x64上仍然是4字节。但是,对象头也参与垃圾回收堆,释放后充当链表中的节点。这需要一个向前和向后的指针,在x64上每个指针都是8字节,因此需要在对象指针之前使用8字节。8 + 8 + 4 + 4 = 24字节。

0

对象除了成员变量之外还有一些开销。在32位的.NET实现中,分配开销似乎为12字节。我记得在64位运行时中它是16字节。

此外,对象分配会对齐到下一个8字节边界。


无论是否对齐,大小仍应反映对象的实际大小。如果填充位于成员之间,则填充将计入大小,但在这种情况下似乎并非如此。 - cHao
在这种情况下,对象本身以外的任何开销细节都不是很相关。大部分开销都在运行时的对象表中,而不是对象本身上。 - cHao

0

在我看来,任何对象都应该有指向其类的某种指针。这可以解释你多出的4或8个字节。

对象的布局实际上是一种实现方式。如果您关心布局,有一些属性专门用于明确告诉 .net 您希望成员位于何处以及如何定位。请查看StructLayoutAttribute


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接