SoFunction
Updated on 2025-03-07

Answers to the memory layout of C# Struct

Question: Please tell me the following struct instance size and memory layout

Copy the codeThe code is as follows:

struct Struct1
{
    public byte a;
    public short b;
    public string c;
    public int d;
}

struct Struct2
{
    public byte a;
    public long b;
    public byte c;
    public string d;
}

struct Struct3
{
    byte a;
    byte b;
    long c;
}

struct Struct4
{
    byte a;
    long b;
    byte c;
}

Let’s look at the answers later to see if there is a big difference from your understanding? In fact, the memory layout of struct and class is determined by the StructLayoutAttribute's constructor parameters: LayoutKind enumeration, struct is added by the compiler, and class is added by the compiler. Sequential can be summarized as follows through experimental data:

1. For structs without reference types: arrange in the order of definition, the memory layout and C++ rules are the same. for example:

Byte a;

Byte b;

Long c;

The size is a, b is filled with 4 bytes and c is filled with 8 bytes

Byte a

Long c

Byte b

The size of a is filled with 8 bytes, c is filled with 8 bytes, b is filled with 8 bytes

2. For structs with reference type: fields greater than 4 bytes -> Reference fields -> Fields less than 4 bytes

For fields smaller than 4 bytes, the memory layout and rule 1 are the same if the size is the same, and if the size is the same, the order in which the memory layout is the same. However, there is one thing to note here that if the field is of the same struct type, then this field is always at the end.

So the answer above is:

Struct1:c(4) -> d(4) -> b(2) ->a(2)

Struct2: b(8) -> d(4) -> a(1)c(1)Fill 2 bytes

Struct3: a(1)b(1)Fill 2 bytes -> c(8)

Struct4: a(1) fills 7 bytes -> b(8)->c(1) fills 7 bytes

If you want to experiment with it yourself, you need to debug it (there are many articles on SOS configuration and introduction to use) Take struct1 as an example:

Struct1s1 = new Struct1();

= 1;          

            = 15;

            = "c";

            = 32;

.load sos

Extension C:\WINDOWS\\Framework\v2.0.50727\ loaded

!clrstack -a

PDB symbol for not loaded

OS Thread Id: 0x15fc (5628)

ESP       EIP   

0041ee3c 03ba01aa Test_Console.()

    LOCALS:

        0x0041ee84 = 0x01b02b0c

        0x0041ee74 = 0x00000020

        0x0041ee68 = 0x00000000

        0x0041ee50 = 0x00000000

0041f104 6ebd1b4c [GCFrame: 0041f104]

.load sos

Extension C:\WINDOWS\\Framework\v2.0.50727\ loaded

!name2ee *!Test_Console.Struct1 //Get the method table address of Struct1

PDB symbol for not loaded

Module: 6d5d1000 ()

--------------------------------------

Module: 00192c5c (Test_Console.exe)

Token: 0x02000012

MethodTable: 00193828

EEClass: 007a45b4

Name: Test_Console.Struct1

!clrstack -a //Get the stack address of the struct1 instance

OS Thread Id: 0x1438 (5176)

ESP       EIP   

003eef0c 008f00c9 Test_Console.()

    LOCALS:

        0x003eef1c = 0x01c12b0c

003ef17c 6ebd1b4c [GCFrame: 003ef17c]

!dumpvc 00193828 0x003eef1c //View the layout of the value type

Name: Test_Console.Struct1

MethodTable 00193828

EEClass: 007a45b4

Size: 20(0x14) bytes

Fields:

      MT    Field   Offset                 Type VT     Attr    Value Name

6d84340c  400001c        a            1 instance        1 a

6d83e910  400001d        8         System.Int16  1 instance       15 b

6d8408ec  400001e        0          0 instance 01c12b0c c

6d842b38  400001f        4         System.Int32  1 instance       32 d

In the memory window, you can see that the memory layout is:

0x003EEF1C  01c12b0c 00000020 0001000f

Here I want to explain that after using dumpvc, a size will be given. Here is 20 bytes, which is 8 bytes more than the result we calculated. My understanding is that because the reference type has an additional 8 bytes (syncblkindex + methodtableaddress), the size here is also added to 8.