为什么struct A的大小不等于具有相同字段的struct B的大小?

时间:2017-02-24 12:15:07

标签: c#

为什么struct A的大小不等于struct B的大小?

我需要做什么,它们的大小相同?

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

输出是:

8
12

4 个答案:

答案 0 :(得分:9)

字段之间有一些填充。填充是使用先前的字段和下一个字段计算的。

此外,这种情况应该是真的:

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

在您的情况下,最大类型为int,其大小为4字节。

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
}

如果两个结构需要相同的大小,可以使用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;
}

您可以使用LayoutKind.SequentialPack = 1CharSet = CharSet.Unicode来获取尺寸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;
}

此外,您可以在不使用unsafe的情况下获得结构大小:

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

答案 1 :(得分:7)

这是因为您的编译器保留在struct的成员之间插入填充的权利,以及最后的一些空格。 (但请注意,在第一个成员之前,填充。)

这样做是为了在易于寻址的内存位置上对齐成员的开头。

特别是,编译器可能在单个charint之间插入填充。偶数char s后跟int可能因此占用的空间少于char后跟int后跟奇数char的空间}第

答案 2 :(得分:6)

这是一个处理器实现细节,.NET非常难以隐藏。变量需要一个存储位置,允许处理器通过单个数据总线操作读取和写入值。这使得变量地址的对齐非常重要。读取单个字节绝不是问题。但是短(2个字节)的地址应该是2的倍数.int(4个字节)的地址应该是4的倍数。理想情况下,long或double(8个字节)的地址是a 8的倍数,但不能总是实现,而不是在32位处理器上。

与RISC内核不同,英特尔和AMD处理器允许不对齐的读写操作。但这可能需要付出代价,可能需要两个数据总线周期才能读取两个字节块,一部分是字节的高位字节,另一部分是低位字节。使用将这些字节混合到正确位置的电路​​。这需要时间,通常需要额外的1到3个时钟周期。 RISC内核上有很多时间来处理总线错误陷阱。

但更严重的是,它破坏了.NET内存模型。它为简单的值类型和对象引用提供了原子性保证。未对齐的读写会破坏这一承诺。它可能会导致撕裂,观察正在写入的部分字节。更糟糕的是,它可以打破垃圾收集器。 GC严重依赖于原子更新的对象引用。

因此,当CLR确定结构或类的布局时,必须确保满足对齐要求。如果不是那么它需要在变量之间留出额外的未使用空间。并且最后可能有额外的空间来确保成员在存储在数组中时仍然对齐。额外空间的通用词是“填充”。

具体到类声明,它有[StructLayout(LayoutKind.Auto)],它可以随机播放成员以实现最佳布局。不是结构,默认情况下它们是LayoutKind.Sequential。除了类和结构之外,静态变量以及方法的参数和局部变量也需要这种对齐保证。但几乎不容易观察到。

答案 3 :(得分:0)

字段的顺序不同;我猜大小是不同的,因为成员被填充(即,它们以一个偶数机器字开始,以便以内存消耗为代价使访问更容易)。