简体   繁体   English

.NET Core 类中的结构对齐

[英]Struct alignment inside a class in .NET Core

I'm trying to understand why a struct that contains only int takes 8 bytes of memory inside a class.我试图理解为什么只包含 int 的结构在类中占用 8 个字节的内存。

considering the following code;考虑以下代码;

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;
    }
}

now, when I'm profiling this two instances with dotMemory i get the following result:现在,当我使用 dotMemory 分析这两个实例时,我得到以下结果:

在此处输入图片说明

Although both int and the intStruct take 4 bytes of memory on the stack, it looks like the class size on the heap is different and that struct is always aligned to 8 bytes.尽管 int 和 intStruct 在堆栈上都占用 4 个字节的内存,但看起来堆上的类大小不同,并且该结构始终与 8 个字节对齐。

What can cause this behavior?什么会导致这种行为?

For classes, default memory layout is "Auto", which means CLR decides itself how to align fields in a class in memory.对于类,默认内存布局是“自动”,这意味着 CLR 自己决定如何在内存中对齐类中的字段。 It's an undocumented implementation detail.这是一个未记录的实现细节。 For some reason unknown to me, it aligns fields of custom value types at a pointer size boundary (so, 8 bytes in 64-bit process, 4 bytes in 32-bit).由于某种我不知道的原因,它将自定义值类型的字段对齐在指针大小边界处(因此,64 位进程中为 8 个字节,32 位中为 4 个字节)。

If you compile that code in 32-bit, you will see that both TwoInt and TwoStruct now take 16 bytes (4 for object header, 4 for method table pointer, and then 8 for fields), because now they are aligned at 4-byte boundary.如果您以 32 位编译该代码,您将看到TwoIntTwoStruct现在都占用 16 个字节(4 个用于对象头,4 个用于方法表指针,然后 8 个用于字段),因为现在它们以 4 字节对齐边界。

At 64-bit case, like in your question, custom value types are aligned at 8-byte boundary, so TwoStruct has layout of:在 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 justTwoInt只是

Object Header (8 bytes)
Method Table Pointer (8 bytes)
IntStruct A (4 bytes)
IntStruct B (4 bytes)

Because int is not a custom value type - CLR does not align it at pointer size boundary.因为int不是自定义值类型 - CLR 不会在指针大小边界对齐它。 If instead of IntStruct we used LongStruct and long instead of int - then both cases would have the same size, because long is 8 bytes and even for custom struct CLR will not need to add any padding to align it at 8-byte boundary in 64-bit.如果不是IntStruct我们使用LongStructlong而不是int -那么这两种情况下将具有相同的大小,因为long为8个字节,甚至自定义结构CLR将不再需要在8字节边界在64加任何填充到对齐-少量。

Here is an interesting article related to the issue.这是一篇与该问题相关的有趣文章 The author develops pretty interesting tool to inspect memory layout of objects directly from .NET code (without external tools).作者开发了非常有趣的工具,可以直接从 .NET 代码(无需外部工具)检查对象的内存布局。 He investigates this same issue and cames to the conclusion above:他调查了同样的问题并得出了上述结论:

If the type layout is LayoutKind.Auto the CLR will pad each field of a custom value type!如果类型布局是 LayoutKind.Auto,CLR 将填充自定义值类型的每个字段! This means that if you have multiple structs that wrap just a single int or byte and they're widely used in millions of objects, you could have a noticeable memory overhead due to padding!这意味着,如果您有多个结构体只包装一个 int 或 byte 并且它们被广泛用于数百万个对象,那么由于填充,您可能会有明显的内存开销!

You can affect the managed layout of a class with StructLayouAttribute with LayoutKind = Sequential IF all fields in this class are blittable (which is the case in this question):可以使用StructLayouAttributeLayoutKind = Sequential 影响类的托管布局,如果此类中的所有字段都是 blittable(在此问题中就是这种情况):

For blittable types, LayoutKind.Sequential controls both the layout in managed memory and the layout in unmanaged memory.对于 blittable 类型,LayoutKind.Sequential 控制托管内存中的布局和非托管内存中的布局。 For non-blittable types, it controls the layout when the class or structure is marshaled to unmanaged code, but does not control the layout in managed memory对于非 blittable 类型,它在将类或结构编组到非托管代码时控制布局,但不控制托管内存中的布局

So as mentioned in comments, we can remove the padding by doing:正如评论中提到的,我们可以通过执行以下操作来删除填充:

[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;
    }
}

Which will actually save us some memory.这实际上会为我们节省一些记忆。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM