Why the size of struct A is not equal size of stru

2020-08-22 05:08发布

问题:

Why the size of struct A is not equal size of struct B?

And what I need to do, they will the same size?

using System;

namespace ConsoleApplication1
{
    class Program
    {
        struct A
        {
            char a;
            char c;
            int b;
        }

        struct B
        {
            char a;
            int b;
            char c;

        }


        static void Main(string[] args)
        {
            unsafe
            {
                Console.WriteLine(sizeof(A));
                Console.WriteLine(sizeof(B));
            }
            Console.ReadLine();
        }
    }
}

Output is :

8
12

回答1:

There are some paddings among fields. Padding is calculated using previous fields and next field.

Also, this condition should be true:

(size of struct) % (size of largest type) == 0

In your case the largest type is int and its size is 4 bytes.

struct A
{
    char a; // size is 2, no previous field, next field size is 2 - no alignment needed
    char c; // size is 2, previous size is 2 -> 2 + 2 = 4, next size is 4 - no alignment needed
    int b;  //size is 4, it is last field, size is 4 + 4 = 8.  

    //current size is 2 + 2 + 4 = 8
    //8 % 4 == 0 - true - 8 is final size
}

struct B
{
    char a; // size is 2, next size is 4, alignment needed - 2 -> 4, size of this field with alignment is 4
    int b;  // size is 4, previous is 4, next size is 2(lower) - no alignment needed
    char c; // size is 2, previous is 4 + 4 = 8 - no alignment needed

    //current size is 4 + 4 + 2 = 10
    //but size should be size % 4 = 0 -> 10 % 4 == 0 - false, adjust to 12
}

If you want same size for two structs you can use LayoutKind.Explicit:

[StructLayout(LayoutKind.Explicit)]
public struct A
{
    [FieldOffset(0)]
    char a;

    [FieldOffset(2)]
    char c;

    [FieldOffset(4)]
    int b;
}

[StructLayout(LayoutKind.Explicit)]
public struct B
{
    [FieldOffset(0)]
    char a;

    [FieldOffset(2)]
    int b;

    [FieldOffset(6)]
    char c;
}

OR

You can use LayoutKind.Sequential, Pack = 1 and CharSet = CharSet.Unicode to get size 8.

[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
public struct A
{
    char a;
    char c;
    int b;
}

[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
public struct B
{        
    char a;
    int b;
    char c;
}

Also, you can get struct size without unsafe:

Console.WriteLine(System.Runtime.InteropServices.Marshal.SizeOf(typeof(A)));
Console.WriteLine(System.Runtime.InteropServices.Marshal.SizeOf(typeof(B)));


回答2:

It's because your compiler reserves the right to insert padding between the members of the struct, plus some space at the end. (But note that padding is not allowed before the first member.)

It does this in order to align the start of members on easily-addressable memory locations.

In particular, a compiler is likely to insert padding between a single char and an int. An even number of chars followed by an int may well therefore take up less space than a char followed by an int followed by an odd number of chars.



回答3:

This is a processor implementation detail, one that .NET tries very hard to hide. Variables need have a storage location that allows the processor to read and write the value with a single data bus operation. That makes the alignment of the variable address very important. Reading a single byte is never a problem. But a short (2 bytes) should have an address that is a multiple of 2. An int (4 bytes) should have an address that is a multiple of 4. Ideally a long or double (8 bytes) have an address that is a multiple of 8, but that cannot always be achieved, not on a 32-bit processor.

Intel and AMD processors allow unaligned reads and writes, unlike RISC cores. But that can come at a cost, it might require two data bus cycles to read two chunks of bytes, part of the upper bytes of a value and part of the lower bytes. With a circuit that shuffles those bytes into the right place. That takes time, typically an extra 1 to 3 clock cycles. Lots and lots of time on a RISC core to handle the bus error trap.

But more severely, it breaks the .NET memory model. It provides an atomicity guarantee for simple value types and object references. Unaligned reads and writes break that promise. It might cause tearing, observing part of the bytes being written. And much worse, it can break the garbage collector. The GC relies heavily on an object reference being updated atomically.

So when the CLR determines the layout of a structure or class, it must ensure the alignment requirement is met. And if it is not then it needs to leave extra unused space between the variables. And perhaps extra space at the end to ensure members are still aligned when stored in an array. Generic word for that extra space is "padding".

Specific to a class declaration, it has [StructLayout(LayoutKind.Auto)], it can shuffle the members around to achieve the best layout. Not a struct, they are LayoutKind.Sequential by default. Beyond classes and structs, this alignment guarantee is also required for static variables and the arguments and local variables of a method. But not nearly as easily observed.



回答4:

The order of the fields is different; I would guess that the size is different as the members are padded (i.e. located in such a way that they begin on an even machine word in order to make access easier at the cost of memory consumption).



标签: c#