• C#-StructLayoutAttribute(结构体布局)


    struct实例字段的内存布局(Layout)和大小(Size)

    在C/C++中,struct类型中的成员的一旦声明,则实例中成员在内存中的布局(Layout)顺序就定下来了,即与成员声明的顺序相同,并且在默认情况下总是按照结构中占用空间最大的成员进行对齐(Align);当然我们也可以通过设置或编码来设置内存对齐的方式.
            然而在.net托管环境中,CLR提供了更自由的方式来控制struct中Layout:我们可以在定义struct时,在struct上运用StructLayoutAttribute特性来控制成员的内存布局默认情况下,struct实例中的字段在栈上的布局(Layout)顺序与声明中的顺序相同,即在struct上运用[StructLayoutAttribute(LayoutKind.Sequential)]特性,这样做的原因是结构常用于和非托管代码交互的情形

    如果我们正在创建一个与非托管代码没有任何互操作的struct类型,我们很可能希望改变C#编译器的这种默认规则,因此LayoutKind除了Sequential成员之外,还有两个成员AutoExplicit,给StructLayoutAttribute传入LayoutKind.Auto可以让CLR按照自己选择的最优方式来排列实例中的字段;传入LayoutKind.Explicit可以使字段按照我们的在字段上设定的FieldOffset来更灵活的设置字段排序方式,但这种方式也挺危险的,如果设置错误后果将会比较严重。下面就看几个示例,算下四个struct各占多少Byte?

    1.[StructLayout(LayoutKind.Sequential)]

    struct StructDeft //C#编译器会自动在上面运用[StructLayout(LayoutKind.Sequential)]
    {
        
    bool i;  //1Byte
        double c;//8byte
        bool b;  //1byte
    }
        

            sizeof(StructDeft)得到的结果是24byte!啊哈,本身只有10byte的数据却占有了24byte的内存,这是因为默认(LayoutKind.Sequential)情况下,CLR对struct的Layout的处理方法与C/C++中默认的处理方式相同(8+8+8=24),即按照结构中占用空间最大的成员进行对齐(Align)。10byte的数据却占有了24byte,严重地浪费了内存,所以如果我们正在创建一个与非托管代码没有任何互操作的struct类型,最好还是不要使用默认的StructLayoutAttribute(LayoutKind.Sequential)特性。 

    2.[StructLayout(LayoutKind.Explicit)]

    [StructLayout(LayoutKind.Explicit)]
    struct BadStruct
    {
        [FieldOffset(
    0)]
        
    public bool i;  //1Byte
        [FieldOffset(0)]
        
    public double c;//8byte
        [FieldOffset(0)]
        
    public bool b;  //1byte
    }

            sizeof(BadStruct)得到的结果是9byte,显然得出的基数9显示CLR并没对结构体进行任何内存对齐(Align);本身要占有10byte的数据却只占了9byte,显然有些数据被丢失了,这也正是我给struct取BadStruct作为名字的原因。如果在struct上运用了[StructLayout(LayoutKind.Explicit)],计算FieldOffset一定要小心,例如我们使用上面BadStruct来进行下面的测试:

    StructExpt e = new StructExpt();
    e.c 
    = 0;
    e.i 
    = true;
    Console.WriteLine(e.c);

            输出的结果不再是0了,而是4.94065645841247E-324,这是因为e.c和e.i共享同一个byte,执行“e.i = true;时”也改变了e.c,CPU在按照浮点数的格式解析e.c时就得到了这个结果.所以在运用LayoutKind.Explicit时千万别把FieldOffset算错了:) 

    3.[StructLayout(LayoutKind.Auto)]
            sizeof(StructAuto)得到的结果是12byte。下面来测试下这StructAuto的三个字段是如何摆放的:

    unsafe
    {
          StructAuto s 
    = new StructAuto();
          Console.WriteLine(
    string.Format("i:{0}", (int)&(s.i)));
          Console.WriteLine(
    string.Format("c:{0}", (int)&(s.c)));
          Console.WriteLine(
    string.Format("b:{0}", (int)&(s.b)));
    }

    // 测试结果:
    i:1242180
    c:
    1242172
    b:
    1242181

            即CLR会对结构体中的字段顺序进行调整,将i调到c之后,使得StructAuto的实例s占有尽可能少的内存,并进行4byte的内存对齐(Align),字段顺序调整结果如下图所示:


    4.空struct实例的Size

    struct EmptyStruct{}

        无论运用上面LayoutKind的Explicit、Auto还是Sequential,得到的sizeof(EmptyStct)都是1byte。 

    结论:
            默认(LayoutKind.Sequential)情况下,CLR对struct的Layout的处理方法与C/C++中默认的处理方式相同,即按照结构中占用空间最大的成员进行对齐(Align)
            使用LayoutKind.Explicit的情况下,CLR不对结构体进行任何内存对齐(Align),而且我们要小心就是FieldOffset
            使用LayoutKind.Auto的情况下,CLR会对结构体中的字段顺序进行调整,使实例占有尽可能少的内存,并进行4byte的内存对齐(Align)。 

  • 相关阅读:
    2020.10.6 提高组模拟
    GMOJ 6815. 【2020.10.06提高组模拟】树的重心
    Codeforces Round #542 [Alex Lopashev Thanks-Round] (Div. 1) D. Isolation
    Forethought Future Cup
    Codeforces Round #543 (Div. 2, based on Technocup 2019 Final Round) D. Diana and Liana
    2020.10.07提高组模拟
    2020.10.05提高组模拟
    9.29 联赛组作业
    JZOJ 3978. 寝室管理
    Centos7下安装netstat的方法
  • 原文地址:https://www.cnblogs.com/peterYong/p/6556555.html
Copyright © 2020-2023  润新知