c语言的结构体字节对齐规则(思路从未如此清晰)

考虑到CPU读取数据的效率,需要对类和结构体的数据成员通过填充空白字节的方式进行某种对齐,这种填充的规则主要有三条。

一、每个数据成员的偏移量必须能够被自身大小整除

如果偏移量不能够被整除,就需要在前面填充字节。例如:

// 32位机
struct A{
    char a;
    int b;
};

char a占用一个字节,int b占用四个字节,但其偏移量为1,不能被其自身大小sizeof(int)=4整除,所以需要在char a后面(也就是int b)前面填充3个字节,这样sizeof(A)就是8个字节大小。

如果b的类型为double,则需要在前面填充7个字节,如果类型为short,则只需要填充一个字节。

这第一个规则是针对每个数据成员来说的,并且我们应用此规则时,是从上往下依据数据成员的顺序逐步填充字节的,所以即使两个结构体的数据成员组成相同,但如果申明的顺序不同,其大小可能也会不同。

二、整个结构体的大小必须能够被最大数据成员整除

在针对每个数据成员应用了规则一之后,还需要针对整个结构体的大小在尾部填充字节,使得整个结构体的大小是其最大数据成员的整数倍。例如:

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

应用规则一,sizeof(B)应该是1+3+4+1=9个字节,但是9不能够被int b的大小4整除,所以尾部需要填充3个字节,这样sizeof(B)=12。如果将b的类型换为double,则应用规则一,sizeof(B) = 1+7+8+1=17,不能被8整除,所以需要在char c后面填充8个字节,sizeof(B)=24。

注意,最大数据成员的计算范围也包括嵌套结构体中的数据成员。

三、如果有结构体A嵌套结构体B,则在应用规则一时,B的偏移量是B的最大数据成员的整数倍

这是规则一的特例,举例:

struct A{
    char a;
    int b;
};

struct B{
    char a;
    A b;
    char c;
};

首先,sizeof(A)=8这没毛病,然后来看B,应用规则一,char a 为1字节大小,然后遇到了嵌套结构体A b,使用第三条规则,A的最大数据成员是int类型,所以A b的起始偏移量必须是4的整数倍,所以在A b之前需要填充3个字节,然后A b自己占用8个字节;最后遇到c,占用一个字节,这样sizeof(B) = 1 + 3 + 8 + 1 = 13;然后收尾使用规则二,B的最大数据成员是嵌套结构体A中的int b,所以整个结构体需要被4整除,需要在char c后面填充3个字节,sizeof(B) = 13 + 3 = 16。

总结:总体来说,结构体字节对齐的规则主要是两条:一条针对每个数据成员提出要求;一条针对结构体整体提出要求。第三条规则不过是规则一的特例,只要掌握好这两条规则,应付一般题型了然于胸,但还有两个特殊的情况可能会出现在某些题目里面。

特殊规则一:位域数据成员

例如:

struct A{
    char a:4;
    int b;
};

A当中的a就是位域成员,它占用了char的4个bits,其余四个bits未用,在这种情况下,虽然其余4个bits未用,但其母类型(即char)需要占用1个字节,则sizeof(A)=8。

若如:

struct B{
    char a:4;
    char b:4;
    int c;
};

则B中的两个位域a和b,用尽了char的1个字节,其大小还是sizeof(B) = 8。

又看:

struct C{
    char a:4;
    char b:5;
    int c;
};

虽然sizeof(C)还是8个字节,但是填充的字节发生了改变。对于a,剩余4bits未用,未用不等于填充;对于b,要求5个bits,a虽然还有4bits未用,但明显不够,于是自然又占用了1个char的字节,然剩余3bits未用,a+b总共2字节;再看c,自身大小为4,故c之前还须补上2个字节。

来个复杂一点儿的:

struct D{
    char t:4=0b0001;
    char k:4=0b0110;
    unsigned short i:8 = 0b01100010; 
    unsigned long long m = 100;
    int j;
};

t和k共用1个字节,i只需要1个字节(8 bits),但其母类型为unsigned short,所以占用2个字节,unsigned long long是8字节,所以需要在i后面填充5个字节,j为4字节,所以应用了规则一之后sizeof(D) = 1 + 2 + 5 + 8 + 4 = 20字节,然应用规则二,20不可以被8整除,末尾需填充4字节,故sizeof(D) = 20 + 4 = 24。


特殊规则二:#pragma pack(n) 预编译指令制定字节对齐方式

直接贴图[3]:

比如#pragma pack(1),就是按1字节对齐,我们知道c语言中所有类型都是1的整数倍,所以该指令意思就是不对齐,什么也不处理,结构体字面上多少大小就是多少大小,十分清爽!

n可以为1, 2, 4, 8和16。

#pragram pack(n) 会改变上面提到的规则一,规则二:

新规则一:每个数据成员a的偏移量必须能够被 min(n, sizeof(a)) 整除

新规则二:整个结构体的大小必须能够被 n 整除

还是来看个例子吧:

#pragma pack(4)

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

struct C{
    char a;
    B b;
    char c;
};

先来看B:指定按4字节大小对齐。B.a占用1字节,B.b占用1字节,但 min(4, sizeof(B.c)) = 4,所以B.b后面填充2个字节,最后sizeof(B)还是8字节。
再来看C:C.a占1个字节,C.b占用8个字节,但是min(4,sizeof(C.B))=4,所以C.a后面填充3个字节,C.c占用1个字节,sizeof(C) = 1 + 3 + 8 + 1 = 13,不能被n=4整除,所以c后面再填充3个字节,即sizeof(C) = 16。
 

参考资料:

[1] Alignment | Microsoft Learn

[2]  GCC Packing-Pragmas

[3] pack pragma | Microsoft Learn 

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值