再讲内存对齐之前,我们先看一个案例:定义两个结构体
struct Struct1 {
double a;
char b;
int c;
short d;
}struct1;
struct Struct2 {
double a;
int b;
char c;
short d;
}struct2;
在main函数中打印结构体的大小
NSLog(@"struct1 = %lu",sizeof(struct1));
NSLog(@"struct2 = %lu",sizeof(struct2));
结果如下
2020-09-09 10:10:54.383238+0800 001-内存对齐原则[3108:169684] struct1 = 24
2020-09-09 10:10:54.383406+0800 001-内存对齐原则[3108:169684] struct2 = 16
为什么出现这样的结果,这里我们就要说到内存对齐
什么是内存对齐
计算机中内存空间是按照字节划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是:在访问特定类型变量的时候通常在特定的内存地址访问,这就需要对这些数据在内存中存放的位置有限制,各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是所谓的内存对齐
为什么需求内存对齐
- 语言特性:在C++中规定了空结构体和空类的内存所占大小为1字节,因为c++中规定,任何不同的对象不能拥有相同的内存地址。 而在C语言中,空的结构体在内存中所占大小为0。(gcc中测试为0,其他编译器不一定)
- 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
内存对齐的规则是什么
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。
- 数据成员的对齐规则可以理解为min(m, n) 的公式, 其中 m表示当前成员的开始位置, n表示当前成员所需要的位数。如果满足条件 m 整除 n (即 m % n == 0), n 从 m 位置开始存储, 反之继续检查 m+1 能否整除 n, 直到可以整除, 从而就确定了当前成员的开始位置。
- 数据成员为结构体:当结构体嵌套了结构体时,作为数据成员的结构体的自身长度作为外部结构体的最大成员的内存大小,比如结构体a嵌套结构体b,b中有char、int、double等,则b的自身长度为8
- 最后结构体的内存大小必须是结构体中最大成员内存大小的整数倍,不足的需要补齐。
搞清楚了内存对齐的规则之后,我们来看下上面那两个结构体是怎么算的
struct Struct1 {
double a; // double类型 长度8 最大长度8 按8对齐 起始offset=0 0%8=0 存放位置区间(0-7)
char b; // char类型 长度1 1<8 按1对齐 起始offset=8 8%1=0 存放位置区间(8)
int c; // int类型 长度4 4<8 按4对齐 起始offset=9 9%4!=0 所以要往后偏移3位9、10、11 12%4=0 存放位置区间(12 13 14 15)
short d; // short类型 长度2 2<8 按2对齐 起始offset=16 16%2=0 存放位置区间(16 17),最大变量的字节数为8,17不是8的总数倍,补齐为24。所以此结构体的所占内存大小为24
}struct1;
如下图
我们在看下struct2
struct Struct2 {
double a; // double类型 长度8 最大长度8 按8对齐 起始offset=0 0%8=0 存放位置区间(0-7)
int b; // int类型 长度4 4<8 按4对齐 起始offset=8 8%4=0 存放位置区间(8 9 10 11)
char c; // char类型 长度1 1<8 按1对齐 起始offset=12 12%1=0 存放位置区间(12)
short d; // short类型 长度2 2<8 按2对齐 起始offset=13 12%2!=0 偏移1位14 14%2=0 存放位置区间(14 15),最大变量的字节数为8,15不是8的总数倍,补齐为16。所以此结构体的所占内存大小为16
}struct2;
如下图
从这里可以看出,结构体的大小和成员的顺序有关系,所以出现了一开始那有的结果,单个的结构体我们搞清楚了。那结构体嵌套又会是什么样的呢?我们看个案例
struct Struct2 {
double a;
int b;
char c;
short d;
struct Struct1 stuct;
}struct2;
打印struct2大小
2020-09-09 13:20:02.282708+0800 001-内存对齐原则[5728:276341] struct2 = 40
根据内存对齐原则我们来分析一下
变量a:占8个字节,从0开始,此时min(0,8),即 0-7 存储 a
变量b:占4个字节,从8开始,此时min(8,4),8可以整除4,即 8-11 存储 b
变量c:占1个字节,从12开始,此时min(12, 1),12可以整除1,即12 存储 c
变量d:占2个字节,从13开始,此时min(13,2),13不能整除2,往后偏移,即 14-15 存储 d
变量 stuct:stuct是一个结构体,根据内存对齐原则二,结构体成员要从其内部最大成员大小的整数倍开始存储,而Struct2中最大的成员大小为8,所以stuct要从8的整数倍开始,当前是从15开始,所以不符合要求,需要往后移动到16,16是8的整数倍,符合内存对齐原则,而stuct的内存大小是24,所以 16-39 存储 stuct
如下图