一、为什么结构体内存对齐
其实我们都知道,结构体只是一些数据的集合,它本身什么都没有。我们所谓的结构体地址,其实就是结构体第一个元素的地址。这样,如果结构体各个元素之间不存在内存对齐问题,他们都挨着排放的。对于32位机,32位编译器(这是目前常见的环境,其他环境也会有内存对齐问题),就很可能操作一个问题,就是当你想要去访问结构体中的一个数据的时候,需要你操作两次数据总线,因为这个数据卡在中间,如图:在上图中,对于第2个short数据进行访问的时候,在32位机器上就要操作两次数据总线。这样会非常影响数据读写的效率,所以就引入了内存对齐的问题。另外一层原因是:某些硬件平台只能从规定的地址处取某些特定类型的数据,否则会抛出硬件异常。
二、结构体内存对齐的规则
下表是Windows XP/DEV-C++和Linux/GCC中基本数据类型的长度和默认对齐模数。
char | short | int | long | float | double | long long | long double | ||
---|---|---|---|---|---|---|---|---|---|
Win-32 | 长度 | 1 | 2 | 4 | 4 | 4 | 8 | 8 | 8 |
Linux-32 | 长度 | 1 | 2 | 4 | 4 | 4 | 8 | 8 | 12 |
Linux-64 | 长度 | 1 | 2 | 4 | 8 | 4 | 8 | 8 | 16 |
MAC-64 | 长度 | 1 | 2 | 4 | 8 | 4 | 8 | 8 | 16 |
1.未指定#pragma pack时
a.第一个成员起始于0偏移处;
b.每个成员按其类型大小和指定对齐参数n中较小的一个进行对齐;
c.结构体总长度必须为所有对齐参数的整数倍,或者理解为最大类型的整数倍;
d.对于数组,可以拆开看做n个数组元素。
2.指定#pragma pack(n)时
a. n必须为0 1 2 4 8 16,其中为0时就相当于未指定#pragma pack
b. 当结构体中的类型小于n时,按该类型的对齐规则对齐,对于大于等于n的类型,按n对齐。
c.结构体总长度: 1.当结构体中的类型全都小于n时,总长度为所有对齐参数的整数倍,或者理解为最大类型的整数倍;2.当结构体中有大于等于n的类型,按n的整数倍对齐。
三、具体举例 运行环境为MAC-64
1.未指定#pragma pack时
例1
struct S1
{
short a1;
short a2;
short a3;
};
struct S2
{
long a1;
short a2;
};
打印结果为 size of s1:6 -- size of s2:16
sizeof(S1) = 6; 这个很好理解,三个short都为2。
例2
struct S1{
int a;
char b;
short c;
};
struct S2{
char b;
int a;
short c;
};
打印结果:size of s1:8 -- size of s2:12
上图中一个单元格表示一个字节 1表示占用,0表示空闲
由上图可以看出要按类型字节长度的整数倍位置对齐,其他位置被空闲。最终所占长度为最大类型的整数倍。
- S1中a 占4个字节。b占1个字节,c占2个字节,但是c要从2的倍数处对齐,因此,正好S1占8个字节,是所有类型的整数倍,占8个字节。
- S2中b占1个字节 占0位置,a 占4个字节 从索引4开始对齐,c占2个字节 从索引8开始对齐,总长为10,但是整个空间应该为所有类型的整数倍,因此需要2个空闲位。总共12个字节
例3 下面是结构体嵌套情况
struct S1{
int a;
double b;
float c;
};
struct S2{
char e[2];
int f;
double g;
short h;
struct S1 s;
};
打印结果:size of s1:24 -- size of s2:48
sizeof(S1) = 24; 这个比较好理解,int为4,double为8,float为4,总长为8的倍数,补齐,所以整个S1为24。
我们看看S2的内存布局:
e | f | g | h | s |
---|---|---|---|---|
1 1 * * | 1 1 1 1 | 1 1 1 1 1 1 1 1 | 1 1 * * * * * * | 1111**** 11111111 11****** |
s 为结构体S1 看做一个整体,他的最大类型double为8位 需要按8的倍数对齐,因此从索引24开始 因此总共48位
例4
struct S1
{
short a;
int b;
};
struct S2
{
char c;
struct S1 d;
double e;
};
打印结果:size of s1:8 -- size of s2:24
内存布局如下:
S1 | a | b |
---|---|---|
1 1 * * | 1 1 1 1 |
S2 | c | d | e |
---|---|---|---|
1 * * * | 11** 1111 | * * * * 1 1 1 1 1 1 1 1 |
综上可知
在结构体嵌套的情况下 结构体看做一个整体,他按他自己的最大成员的类型进行对齐,最终按整个结构体中最大类型对齐。
指定#pragma pack(n)时(#pragma pack 是编译预处理指令,可以指定按多少字节对齐)
a. n必须为0 1 2 4 8 16,其中为0时就相当于未指定#pragma pack
b. 当结构体中的类型小于n时,按该类型的对齐规则对齐,对于大于等于n的类型,按n对齐。
c.结构体总长度: 1.当结构体中的类型全都小于n时,总长度为所有对齐参数的整数倍,或者理解为最大类型的整数倍;
2.当结构体中有大于等于n的类型,按n的整数倍对齐。
例1
#pragma pack(1)
struct S1
{
char a;
short b;
short c;
};
struct S2
{
long d;
short e;
char f;
};
打印结果为:size of s1:5 -- size of s2:11
很好理解n=1时,按顺序排放
pragma pack(2) 时的结果:size of s1:6 -- size of s2:12
此时 S1中的short类型正好等于2个字节 因此按2个字节对齐,S2中long类型占8字节大于2 因此按2字节对齐
pragma pack(4) 时的结果: size of s1:6 -- size of s2:12
此时S1中的所有类型长度都小于4,因此按S1中最大类型short对齐。S2中long类型占8字节大于4 因此按4字节对齐
pragma pack(8) 时的结果:size of s1:6 -- size of s2:16
此时S1中的所有类型长度都小于8,因此按S1中最大类型short对齐。
S2中long类型占8字节正好等于8 因此按8字节对齐,因此为16字节
pragma pack(16) 时的结果:size of s1:6 -- size of s2:16
此时S1还是所有类型小于16,因此按S1中最大类型short对齐。
S2中long类型占8字节小于16 因此按S2中最大类型long 8字节对齐,因此为16字节.
例2 结构体嵌套情况
#pragma pack(1)
struct S1
{
short a;
int b;
};
struct S2
{
char c;
struct S1 d;
double e;
};
打印结果:size of s1:6 -- size of s2:15
很好理解所有结构体顺序排放
pragma pack(2) 的时候:size of s1:6 -- size of s2:16
S1还是6字节。
S2的布局情况如下
S2 | c | d | e |
---|---|---|---|
1 * | 11 1111 | 11111111 |
pragma pack(4) 的时候: size of s1:8 -- size of s2:20
此时S1中的int类型正好是4 因此按4字节对齐 因此占8字节
S2的内存布局如下:
S2 | c | d | e |
---|---|---|---|
1 *** | 11**1111 | 11111111 |
此时因为d是S1类型 S1看做一个整体,在S1中按4字节对齐 因此从4开始布局,e由于大于pack(4) 因此按4字节对齐排序,从12索引出开始布局。因此总共20字节 也是4的整数倍。
pragma pack(8) 的时候: size of s1:8 -- size of s2:24
此时S1中的int类型小于8,因此按S1中最大类型int对齐
S2的内存布局如下:
S2 | c | d | e |
---|---|---|---|
1 *** | 11**1111 | **** 11111111 |
此时S2中最大类型(double)的成员e正好等于8,因此按8字节对齐,因此从索引16出开始布局,最终为24个字节
pragma pack(16) 的时候: size of s1:8 -- size of s2:24
此时S2中最大类型(double)成员e小于16,因此按8字节对齐,因此情况和pragma pack(8) 的时候一样。