.NET Core 中类内部的结构对齐

2024-03-09

我试图理解为什么只包含 int 的结构在类中占用 8 个字节的内存。

考虑以下代码;

static void Main()
{
    var rand = new Random();

    var twoIntStruct = new TwoStruct(new IntStruct(rand.Next()), new IntStruct(rand.Next()));
    var twoInt = new TwoInt(rand.Next(), rand.Next());

    Console.ReadLine();
}

public readonly struct IntStruct
{
    public int Value { get; }

    internal IntStruct(int value)
    {
        Value = value;
    }
}

public class TwoStruct
{
    private readonly IntStruct A;
    private readonly IntStruct B;

    public TwoStruct(
        IntStruct a,
        IntStruct b)
    {
        A = a;
        B = b;
    }
}

public class TwoInt
{
    private readonly int A;
    private readonly int B;

    public TwoInt(
        int a,
        int b)
    {
        A = a;
        B = b;
    }
}

现在,当我使用 dotMemory 分析这两个实例时,我得到以下结果:

虽然 int 和 intStruct 在堆栈上占用 4 个字节的内存,但看起来堆上的类大小不同,并且该结构始终与 8 个字节对齐。

什么会导致这种行为?


对于类,默认内存布局是“自动”,这意味着 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;
    }
}

这实际上会节省我们一些内存。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

.NET Core 中类内部的结构对齐 的相关文章

随机推荐