C#_字节对齐

一、概念

  • 指定对齐值:编译器或者程序员指定的对齐值,packLen,.Net中默认为4。
  • 默认对齐值:结构体中每一个数据成员及结构体本身都有默认对齐值,记为defaultLen
  • 成员偏移量:即相对结构体起始位置的长度,记为offset
  • 成员长度:结构体中每个数据成员的长度(注意结构体成员为补齐之后的长度),记为memberLen
  • 有效对齐值:validLen=min(packLen,defaultLen)。

两个规则:

  • 对齐规则:offset%validLen=0,其中validLen=min(packLen,defaultLen)。
  • 填充规则:如成员变量不遵守对齐规则,则不需要对齐补齐;再其前面填充一些字节保证该成员对齐。需填充的字节数记为pad。
  • 结构体的总大小,也就是sizeof的结果,必须是其内部成员最大有效对齐值的整数倍,不足的要补齐

二、示例

1、成员之前需要补齐

    public struct Person
    {
        public byte A ;
        public UInt16 B;
    }
        static void Main(string[] args)
        {
            Person p = new Person();
            unsafe
            {
                Console.WriteLine(((int)&p.A).ToString("X"));//D777EAF0
                Console.WriteLine(((int)&p.B).ToString("X"));//D777EAF2
            }
            Console.WriteLine(Marshal.SizeOf(p));//4
        }

指定对齐值packLen为4(默认的没有写出来)。有效对齐值=min(packLen,defaultLen)。

  1. A的偏移量为0,该字段有效对齐值为1,满足对齐方式,占用1byte。
  2. 下一个可用地址的偏移量为1,不是字段B的有效对齐值的倍数,所以需要补齐一个字节使偏移量变为2。所以需要字段A后面补足一个字节,字段A占两个字节。保证B的偏移量为2,满足对齐方式,B占2字节。
  3. 所有成员都分配了空间,空间总大小为2+2=4byte,是最大有效对齐值2的倍数,所以最终大小为4byte。

1、结构体最后需要补齐

    public struct Person
    {
        public byte A;
        public UInt16 B;
        public byte C;
    }
        static void Main(string[] args)
        {
            Person p = new Person();
            unsafe
            {
                Console.WriteLine(((int)&p.A).ToString("X"));//47BAE920
                Console.WriteLine(((int)&p.B).ToString("X"));//47BAE922
                Console.WriteLine(((int)&p.C).ToString("X"));//47BAE924
            }
            Console.WriteLine(Marshal.SizeOf(p));//6
        }
  1. 字段A偏移量为0,有效对齐值为1,满足对齐方式,占1一个字节。
  2. 下一个字段的偏移量为1,有效对齐值为2,不满足对齐方式。所以需要在A后面补齐一个字节,使下一个字段的偏移量为2。
  3. 所以字段A需要占两个字节。B的偏移量为2,B占两个字节。
  4. C字段的偏移量为4,有效对齐值为1,满足对齐方式,占用一个字节。
  5. 总成员占2+2+1=5,不是最大有效对齐值2的倍数,所以最后一个需要补齐1个字节。总字节为2+2+1+1=6。

3、结构体中成员顺序

    public struct Person
    {
        public byte A;
        public byte C;
        public UInt16 B;
    }
        static void Main(string[] args)
        {
            Person p = new Person();
            unsafe
            {
                Console.WriteLine(((int)&p.A).ToString("X"));//FE57EAC0
                Console.WriteLine(((int)&p.B).ToString("X"));//FE57EAC2
                Console.WriteLine(((int)&p.C).ToString("X"));//FE57EAC1
            }
            Console.WriteLine(Marshal.SizeOf(p));//4
        }

还是上面的一个例子,我们调整了一下顺序,正好把成员C放在了A后面,这样A后面就不需要补齐一个字节了,结构体总共占4字节。所以在结构体成员中尽量按所占字节从小到大排列,这样可能会使整个结构体占用空间变小

三、StructLayout特性

1、LayoutKind枚举类型。

允许的值有StructLayout.Auto 、StructLayout.Sequential 和StructLayout.Explicit。默认的内存排列就是Sequential,也就是按成员的先后顺序排列;Explicit可以精确布局内存,需要用FieldOffset()设置每个成员的位置,这样就可以实现类似c的公用体的功能。

2、CharSet

CharSet定义在结构中的字符串成员在结构被传给DLL时的排列方式。可以是Unicode、Ansi或Auto,默认为Auto。指示在默认情况下是否应将类中的字符串数据字段作为 LPWSTRLPSTR 进行封送处理。

3、Pack

Pack定义了结构的封装大小。可以是1、2、4、8、16、32、64、128或特殊值0。特殊值0表示当前操作平台默认的压缩大小。

4、Size

指示类或结构的绝对大小

5、示例

修改指定对齐值

[StructLayout(LayoutKind.Sequential,Pack = 1)]
public struct Person
{
    public double A;
    public byte C;
    public byte B;
}
    static void Main(string[] args)
    {
        Person p = new Person();
        unsafe
        {
            Console.WriteLine(((int)&p.A));//-380116104
            Console.WriteLine(((int)&p.B));//-380116095
            Console.WriteLine(((int)&p.C));//-380116096
        }
        Console.WriteLine(Marshal.SizeOf(p))//10
    }

这里就不做解释了,只是修改了指定对齐值为1。

精确布局内存

    [StructLayout(LayoutKind.Explicit,Size =12)]
    public struct Person
    {
        [FieldOffset(0)]
        public double A;
        [FieldOffset(8)]
        public byte B;
        [FieldOffset(9)]
        public byte C;
        [FieldOffset(10)]
        public UInt16 D;
    }
    static void Main(string[] args)
    {
        Person p = new Person();
        unsafe
        {
            Console.WriteLine(((int)&p.A));//1402463224
            Console.WriteLine(((int)&p.B));//1402463232
            Console.WriteLine(((int)&p.C));//1402463233
            Console.WriteLine(((int)&p.D));//1402463234
        }
        Console.WriteLine(Marshal.SizeOf(p));//12
    }

Explicit一般与Size一起使用,不然最终的大小不是期待的。

联合体

[StructLayout(LayoutKind.Explicit,Size =1)]
public struct Person
{
    [FieldOffset(0)]
    public byte A;
    [FieldOffset(0)]
    public byte B;
}
    static void Main(string[] args)
    {
        Person p = new Person();
        unsafe
        {
            Console.WriteLine(((int)&p.A));//1689773680
            Console.WriteLine(((int)&p.B));//1689773680
        }
        Console.WriteLine(Marshal.SizeOf(p));//1

        p.A = 100;
        Console.WriteLine(p.B);//100
    }
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值