[.NET] Detailed explanation of structure layout and specific method of structure memory alignment

memory layout

Generally, we don't need to care about the memory layout, because we directly access the structure through fields or attributes, but when operating with unmanaged libraries, sometimes we need to pay attention to the layout of the structure. Only when the layout is consistent can we guarantee When passing the structure pointer directly, the unmanaged code can access the members normally.

[StructLayout(LayoutKind.Sequential)]    // 声明 StructLayout 
struct MyStruct
{
    
     }

Sequential layout (Sequential)

Sequential layout is to place them in the memory one by one according to the order in which you declare the members in the structure, but it should be noted that these members are not next to each other, they may have memory alignment, but we will discuss this below Talk about it in detail.

Explicit layout (Explicit)

In this layout, you need to specify the offset of each field in the structure. For example, if you have a structure with two ints in it, you want to separate the two ints by 2 words Section size, then you only need to specify an offset of 0 for the first structure, and an offset of 6 for the second structure.

Automatic layout (Auto)

In this layout, you should not interoperate with unmanaged, because for performance, the order of members in the structure will be automatically adjusted. For example, the following obviously cannot be done without adjusting the order and adding space To memory-aligned structures, the order of its members will be adjusted.

[StructLayout(LayoutKind.Auto)]
struct SomeIntegers
{
    
    
    byte AByte;
    short AShortInteger;
    byte AnotherByte;

    // 你实际得到的可能是 byte, byte, short 这样的一个结构体
}

memory alignment

When you use sequence layout, structure members will have memory alignment, and when memory alignment is performed, the following behaviors will occur:

  1. The memory offset of a member, which should be divisible by its own size
  2. If a member occupies more memory than the size of the pack, its offset is no longer required to be divisible by its own size, but only by the pack size.

what is a package

Packet is the required size of memory alignment. For example, in Windows, the default is 8-byte alignment. For example, some data larger than eight bytes can be aligned in memory according to 8 bytes.

offset requirement

For example, if we have an int (32-bit), then its memory offset should be 4, 8, 12 and other values ​​that can be divisible by 4. Similarly, the offset of long (64-bit) should also be Should be 8, 16, 32 these.

For example, in the following structure, in order to achieve an offset of 2, member B creates a 1-byte gap after member A.

[StructLayout(LayoutKind.Sequential)]
struct SomeIntegers
{
    
    
    byte A;     // 1 byte
                // 1 byte
    short B;    // 2 bytes
}

Member takes up more than package

For example, when using a packet size of 8 bytes, and 4 bytes have been used in a packet, if you want to load a long (8 bytes), then obviously this packet can no longer hold this field , then this field will be placed in the next package.

For example, in the following structure, in order to achieve 8-byte alignment of member B, there is a 4-byte gap between it and the first member.

[StructLayout(LayoutKind.Sequential)]
struct SomeIntegers
{
    
    
    int A;    // 4 bytes
              // 4 bytes
    long B;   // 8 bytes
}

But when you specify Pack as 4, this long no longer requires that the offset be divisible by 8, but by 4.

[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct SomeIntegers
{
    
    
    int A;    // 4 bytes
    long B;   // 8 bytes (B 与 A 之间的空隙没有了)
}

Therefore, when you don't want this structure to have any gaps, or you don't want this structure to have memory alignment, specifying Pack = 1 can solve the problem. Because this will cause the offsets of all fields to be divisible by 1, So they don't have any requirements for the offset.

member trailing blank

A structure tail will also generate some free, unused bytes, the size of which depends on the size of the largest member in the structure .

For example, in my structure, there is one long, one byte, the maximum member size is 8, so the size of the structure must be a multiple of 8.

[StructLayout(LayoutKind.Sequential)]
struct TwoIntegers  // 大小共计 16 bytes
{
    
    
    long A;  // 8 bytes
    byte B;  // 1 byte
             // 7 bytes
}

When structs are nested

For example, one of my structures contains another structure, so how to align the memory at this time?

  1. The resulting gap between a struct field and the previous field depends on the largest alignment size in the struct.
  2. The empty memory at the end of the structure field itself will still be reserved in the outer structure

1. Gap before structure field

For example, in a structure, there is a intfield and a bytefield, and its maximum relative size is 4, that is to say, when this structure is used as a member of other structures, it will also use 4 as the alignment size.

[StructLayout(LayoutKind.Sequential)]
struct SomeIntegers
{
    
    
    public byte A;              // 1 byte
                                // 3 bytes (结构体最大对齐是 4, 所以这里留出了 4 - 1 = 3 个字节)
    public TwoIntegers B;       // 8 bytes
    public byte C;              // 1 byte
                                // 3 bytes
}

[StructLayout(LayoutKind.Sequential)]
struct TwoIntegers
{
    
    
    int A;     // 4 bytes
    byte B;    // 1 
}

2. Leave the tail of the structure blank

Even if the space at the end of the member of the structure can accommodate the next member, it will not do so. "The memory space of the structure itself is completely inviolable"

[StructLayout(LayoutKind.Sequential)]
struct SomeIntegers
{
    
    
    public byte A;              // 1 byte
                                // 3 byte
    public TwoIntegers B;       // 8 bytes
    public byte C;              // 1 byte   (尽管上一个结构体字段后有留空, 但这段留空不会被重复利用)
                                // 3 bytes  (所有成员的最大大小是 4, soyi这里留
}

[StructLayout(LayoutKind.Sequential)]
struct TwoIntegers
{
    
    
    int A;    // 4 bytes
    byte B;   // 1 byte
              // 3 bytes
}

realize union

There is a union in C++, which enables multiple fields to share some data. In C#, if you want to achieve this, you can use explicit layout.

For example, in the structure defined by C++ below, there are two fields A and B, they share the same memory area.

struct SomeIntegers
{
    
    
    union {
    
    
        int A;
        int B;
    };
};

To achieve this in C#, you can use:

[StructLayout(LayoutKind.Explicit)]
struct SomeIntegers
{
    
    
    [FieldOffset(0)]
    int A;

    [FieldOffset(0)]
    int B;
}

Or a C++ struct like this:

struct SomeIntegers
{
    
    
    union {
    
    
        int A;
        struct {
    
    
            short Head;
            short Tail;
        };
    };
};

It can be written in C# like this:

[StructLayout(LayoutKind.Explicit)]
struct SomeIntegers
{
    
    
    [FieldOffset(0)]
    int A;               // 占 4 字节

    [FieldOffset(0)]
    short Head;          // 占 2 字节

    [FieldOffset(2)]
    short Tail;          // 占 2 字节
}

Guess you like

Origin blog.csdn.net/m0_46555380/article/details/128623163