对于类,默认内存布局是“自动”,这意味着 CLR 自行决定如何在内存中对齐类中的字段。这是一个未记录的实现细节。由于某种我不知道的原因,它将自定义值类型的字段对齐在指针大小边界(因此,64 位进程中为 8 个字节,32 位进程中为 4 个字节)。
如果您以 32 位编译该代码,您将看到TwoInt
and TwoStruct
现在占用 16 个字节(4 个用于对象头,4 个用于方法表指针,然后 8 个用于字段),因为现在它们在 4 字节边界对齐。
在 64 位情况下,就像您的问题一样,自定义值类型在 8 字节边界对齐,因此TwoStruct
布局为:
Object Header (8 bytes)
Method Table Pointer (8 bytes)
IntStruct A (4 bytes)
padding (4 bytes, to align at 8 bytes)
IntStruct B (4 bytes)
padding (4 bytes)
And TwoInt
is just
Object Header (8 bytes)
Method Table Pointer (8 bytes)
IntStruct A (4 bytes)
IntStruct B (4 bytes)
Becauseint
不是自定义值类型 - CLR 不会将其与指针大小边界对齐。如果代替IntStruct
我们用了LongStruct
and long
代替int
- 那么两种情况都会有相同的大小,因为long
是 8 字节,即使对于自定义结构,CLR 也不需要添加任何填充来将其在 64 位中的 8 字节边界对齐。
这是一个有趣的文章 https://devblogs.microsoft.com/premier-developer/managed-object-internals-part-4-fields-layout/与该问题相关。作者开发了非常有趣的工具来直接从 .NET 代码检查对象的内存布局(无需外部工具)。他研究了同样的问题并得出了上述结论:
如果类型布局是 LayoutKind.Auto,则 CLR 将填充 a 的每个字段
自定义值类型!这意味着如果您有多个结构
仅包装一个 int 或 byte,它们被广泛用于数百万个
对象,由于填充,您可能会产生明显的内存开销!
You 可以影响 https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.structlayoutattribute?view=net-5.0类的托管布局StructLayouAttribute
with LayoutKind = Sequential
如果此类中的所有字段都是 blittable(本问题就是这种情况):
对于 blittable 类型,LayoutKind.Sequential 控制中的布局
托管内存和非托管内存中的布局。对于不可直接传送的
类型,它控制类或结构被封送时的布局
到非托管代码,但不控制托管内存中的布局
因此,正如评论中提到的,我们可以通过执行以下操作来删除填充:
[StructLayoutAttribute(LayoutKind.Sequential, Pack = 4)]
public class TwoStruct
{
private readonly IntStruct A;
private readonly IntStruct B;
public TwoStruct(
IntStruct a,
IntStruct b)
{
A = a;
B = b;
}
}
这实际上会节省我们一些内存。