为什么需要内存对齐?
cpu在存取指令时,以字长为单位进行存取,32位机器的默认字长为4字节,64位机器的默认字长为8字节。cpu每次读u取时,都从对齐字长的整数倍开始读取,而存放数据时,cpu以对齐字长的整数倍进行存放,这要求我们对struct或class内的数据排布提出了要求。
假如没有内存对齐,数据被任意存放,cpu读取数据时,cpu需要先取出若干个对齐字长的内存,然后去掉开头和尾部不属于所需数据的字节,最后留下的数据块合并后再存入寄存器。考虑内存对齐的作用,cpu存取数据时可以直接取出若干对齐字节大小的数据,然后这直接存入寄存器,这显然比没有内存对齐要高效得多。
内存对齐规则
内存对齐时,struct第一个数据成员放置在offset为0的位置,后续数据成员的位置需要先确定对齐单位大小,每个数据成员的offset都是对齐单位的整数倍。
普通数据成员对齐
比如下面的代码中,对齐单位为8,char p1[11]大小本来应该为11个字节,但为了补齐后续缺失的字节,p1占据的大小会扩大为8x2=16字节,p2占据8字节,因此a的大小为24字节。
struct a{
char p1[11]; //0~15
long long p2; //16~23
};
那么如何确定对齐单位大小呢?前面说过,以64位机器为例,默认字长为8字节,也通过下面的方式可以显式指定默认字长为n。
#pragma pack(n)
对每个数据成员,对齐单位的大小为默认字长和数据成员本身长度中较小的那个,比如下面的结构体。
struct b{
char p1; //1<8,对齐单位为1,0
char p2; //1<8,对齐单位为1,1
short p3; //2<8,对齐单位为2,2~3
int p4; //4<8,对齐单位为4,4~7
};//结构体大小为8
我们将上面的结构体稍微改变,就会看到变化。
struct c{
char p1; //1<8,对齐单位为1,0
short p2; //2<8,对齐单位为2,2~3
int p3; //4<8,对齐单位为4,4~7
int p4; //4<8,对齐单位为4,8~12
};
struct结构体对齐
在每个数据成员都对齐后,struct本身也要对齐,struct会取出数据成员中最长的那个,与默认字长比较,取较小的那个作为对齐单位。
#pragma pack(4)
struct d{
int p1; //4=4,对齐单位为4,0~3
char p2; //1<4,对齐单位为1,4
short p3; //2<4,对齐单位为2,offset从6开始,6~7
char p4; //1<4,对齐单位为1,7
};//4=4,对齐单位为4,0~11
上面的结构体中,数据成员占据9个字节,但由于结构体对齐单位为4,因此扩充到了12个字节。
结构体中包含结构体时的对齐,与普通数据成员对齐规则相同,但结构体的长度按其最长的数据成员计算。
struct e{
struct d p2; //最大数据成员长度为4,4=4,以4为对齐单位
char p1[5];
};//5>4,对齐单位为5,结构体大小为20
union对齐
unoin是c++中一种特殊的结构体,这种结构体中的所有数据成员的起始地址相同,其内存大小按最长的数据成员计算:
union f{
char p1;
char p2;
short p3;
int p4;
};//内存按int大小算,为4
union同样也需要考虑内存对齐,规则与struct大致相同:
union g{
int p1;
short p2;
char p3[7];
};//7>4,对齐单位为4,结构体大小为8
对于多种结构体嵌套在一起的情况,也只需要通过上面介绍的规则分析,这里不再讨论。