简单易懂的C结构体对齐原则

简单易懂的C结构体对齐原则

结构体的对齐原则非常简单,只需要理解两个点就能透彻理解结构体对齐。分别是:

  1. 结构体内成员的对齐长度,该长度就是该成员的类型长度;
  2. 结构体的对齐长度,该长度默认情况下是成员类型长度最长的那成员的类型长度;

详解

  • 原则1:(没有使用#pragma pack指令)

    结构体每个成员都有自己的对齐原则,该成员的对齐原则为该成员的类型长度的整倍数。

    A. 简单示例如下(暂时先不关注结构体的总长度):

    struct A
    {
    	char a;
    	short b;
    	int c;	
    }
    
    

    在上述结构体中,

    • 成员a的对齐长度为1,前面没有成员,自然就是第一个;
    • 成员b的对齐长度为2,此时已经存在的结构体长度为1,1/2 不是整倍数,那么就需要进行填充;填充一个字节就能是2的整倍数,算法就是(1%2)+1=2, 因此成员b放进去之后呢,该结构体的长度就为4了;
    • 成员c的对齐长度为4,前面的长度已经就是4了,4/4为整倍数,因此不需要填充。
    • 那么此时结构体的长度就为8

    B. 复杂示例如下(暂时先不关注结构体的总长度):

    struct B
    {
        short a;
        char b[11];
        short c[10];
        double d;
        int e;
    }
    

    这个是一个稍微复杂的例子,不过同样很简单。

    • 成员a的对齐长度为2,占两个字节,

    • 成员b的对齐长度为1,虽然b为数组,但是这里可以看成是多个连续的变量,因此他的对齐长度为1;当前结构体的长度为2,2/1是整倍数,因此不需要填充,然后当前结构体的长度为2+11 = 13;

    • 成员c的对其长度为2,同理,看做是一个连续的short成员;此时,当前结构体的长度为13,当前c的对齐长度为2, 13 /2 = 6.5,不是一个整倍数,那么就需要填充,算法为(2-(13%2))+13 = 14,因此成员c的起始位置为14,此时结构体的长度为14+ 10*2 = 34;((2-(13%2))解释,2为当前的对齐长度,13%2为取余数,那么用2减去余数就是差多少个到2,注意看下面以8为对齐长度的例子)

      (好了,如果认真看到这里的基本就明白是怎么回事了。如果自己还能动手算算的更棒了);

    • 成员d的对齐长度为8,此时结构体的长度为34,34/8 不能被整除,于是需要填充,填充字节数为 :8-(34%8), 起始位置:(8-(34%8))+34=40,因此成员d的起始位就是40了;(8-(34%8),34/8取余为2, 8-2当前的起始位置到8 还差6个字节)

    • 成员e的对齐长度为4,此时结构体的 48, 48/4可以整除,所以不需要填充。因此成员e的起始位置就为48了。因此此时结构体的长度就位52了。

    • 结构体中有结构体的情况看最下面的实例.

  • 原则2:(可以说是非常简单了)

    在没有使用#pragma pack(n)的情况下,结构体的总长度默认以最长的成员的长度作为对齐长度的。

    此处以上两个例子继续为例好了。上述的例子里面我们都没有计算整个结构体的总长度

    • 例子A

      当前A 的结构体长度为8,当前结构体中最大长度成员的对齐是c,对齐长度为4,按照上述的原则,当前结构体的对齐长度为4,那么8/4可以整除,那么当前结构体不需要填充。

    • 例子B

      当前B的结构体长度为52,然后结构体中最长的成员为d,该成员的长度为8,于是当前结构体的对齐长度为8,那么52/8 = 6.5,不是一个整倍数。那么说明需要填充操作。填充字节数:8 - (52%8), 8 - 4 = 4,则当前结构体需要填充4个字节,因此最终结构体B的总长度就为 52 + 4 = 56个字节。

      在这里插入图片描述

#pragma pack(n), n ∈ {124816}

#pragma pack改变的是结构体内所有成员的对齐长度原则,意思就是说,如果使用了该指令,上例中结构体成员的对齐长度也由该指令指定。

例如示例A,如果加上了#pragma pack(1),结构体长度则为 1 + 2 + 4 = 7

在这里插入图片描述

示例B呢么,如果加上了#pragma pack(1), 那么结构体的长度则为:2+11+2*10+8+4=45

在这里插入图片描述

那么如果示例B来个pragma pack(3), 那就不好意思了。该指令的n的取值范围为 {124816}中的任意一个,其他任何非该取值范围的值都将使用当前的默认对齐当时。

#pragma pack(push/pop)

该指令代表pack的开始和结束,在此范围内的#pragma pack(n)离开该范围之后,就会恢复到#pragma pack(push)之前的对齐值。

常见理解误区

  1. 结构体的对齐长度为 最长成员类型长度;

    解答:以上概念没什么问题,只是以上原则只是对上文中原则2的解释,所说的就只是结构体的对齐长度,而没有包含成员的对齐原则; 结构体的对齐长度可以通过预编译指令#pragma pack(n)进行改变;被#pragma pack(n)包含的结构体的成员和结构体的对齐值都为n;

  2. 结构体中包含结构体的情况中,那么外结构体的对齐长度为内结构的长度;

    解答:严格来说,这个说法是不正确的。在多层结构体中,外结构体的对齐长度仍然遵循原则2,内结构体的对齐长度为该结构体的对齐长度;

在这里插入图片描述

好了,不会算的多看两遍。动手就好。

  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值