1、内存对齐是什么?
在计算机中,内存大小的基本单位是字节,理论上来讲,可以从任意地址访问某种基本数据类型。
但是实际上,计算机并非按照字节大小读写内存,而是一定倍数(8、16倍)的字节块来读写内存。因此,编译器会对基本数据类型的合法地址作出一些限制,即它的地址必须是一定倍数(8、16倍)。那么就要求各种数据类型按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
2、内存对齐的原则
内存对齐应该是编译器的管辖范围,编译器为程序中的每个数据单元安排在适当的位置上,方便计算机快速高效的进行读取数据。
每个平台的编译器都有自己的对齐系数和相应的对齐规则。在iOS中的64位架构下,对齐系数就是8个字节。
2.1 数据成员对齐
结构体(struct)或联合体(union)
中的成员变量中,首个成员变量放在偏移量为0的位置上,后面的成员变量的对齐偏移量是取指定对齐系数和本身该成员变量所占用大小中的较小值,即min(对齐系数,成员变量的内存大小 )
2.2 数据整体对齐
在 结构体(struct)或联合体(union)
的数据成员中的成员变量
完成自身的对齐之后,整个 结构体(struct)或联合体(union)
也需要进行字节对齐处理,一般为min(对齐系数,最大成员变量的内存大小 )的整数倍。
结合上述原则1、2,可以推断出下面的常用原则,以结构体
为例:
- 结构体变量的首地址是其
最长基本类型成员
的整数倍; - 结构体每个成员相对于结构体首地址的偏移量(offset)都是
成员大小
的整数倍,如不满足,对前一个成员填充字节以满足; - 结构体嵌套结构体的内存大小必须是
结构体中最大成员内存大小
的整数倍,不足的需要补齐。
结合上述分析, 可以总结出内存对齐原则:
-
【1】 数据成员的对齐规则可以理解为
min(m, n)
的公式, 其中m
表示当前成员的开始位置
,n
表示当前成员所需要的占用字节个数
。如果满足条件m 整除 n
(即m % n == 0
),n
从m
位置开始存储, 反之继续检查 m+1 能否整除 n
, 直到可以整除, 从而就确定了当前成员的开始位置。 -
【2】数据成员为结构体:当结构体嵌套了结构体时,作为数据成员的
结构体的自身长度
作为外部结构体的最大成员的内存大小
,比如结构体a嵌套结构体b,b中有char、int、double等,则b的自身长度为8 -
【3】结构体的内存大小必须是结构体中
最大成员内存大小的整数倍
,不足的需要补齐。
image.jpeg下表示各数据类型在iOS中占用的内存大小
结构体内存对齐
接下来我们分析下结构体的内存分布
struct MGStruct1 {
// 占字节数 起始位置 占的位置
double a; // 8 0 (0 1 2 3 4 5 6 7)
char b; // 1 8 (8)
int c; // 4 9 9 10 11 (12 13 14 15)
short d; // 2 16 (16 17)
}struct1;
// 结构体内部需要的内存大小为: 18
// 最大属性占用内存 : 8
// 结构体整数倍: 24
struct MGStruct2 {
// 占字节数 起始位置 占的位置
double a; // 8 0 (0 1 2 3 4 5 6 7)
int c; // 4 8 (8 9 10 11)
char b; // 1 12 (12)
short d; // 2 13 (14 15)
}struct2;
// 结构体内部需要的内存大小为: 16
// 最大属性占用内存 : 8
// 结构体整数倍: 16
struct MGStruct3 {
// 占字节数 起始位置 占的位置
int c; // 4 0 (0 1 2 3)
double a; // 8 4 4 5 6 7 (8 9 10 11 12 13 14 15)
char b; // 1 16 (16)
short d; // 2 17 17 (18 19)
}struct3;
// 结构体内部需要的内存大小为: 20
// 最大属性占用内存 : 8
// 结构体整数倍: 24
控制台打印输出可以到处:
24 16 24
image.png
按照前面分析的内存对齐原则可以知道内存分布情况
image.png注:空白部分为补齐的内存位置
以 struct1
为例做出分析内存大小过程如下:
-
变量a
是double
类型, 占用8字节 ,从 0 开始, 占用位置0, 1, 2, 3, 4, 5, 6, 7
-
变量b
是char
类型, 占用1字节,从8位置开始, 占用位置8
-
变量c
是int
类型, 占用4字节,按理说应该从 9 位置开始,但是按照内存对齐原则9不能被4整除,需要向后偏移,知道找到能整除4的位置,位置12可整除4, 所以占用位置12, 13 14 15
-
变量d
是short
类型,占用2字节,从16位置开始 ,占用位置16, 17
按照内存对齐原则分析得出:struct1
最大变量字节数为8,分析完成4个属性(a, b, c,d)需要18字节
内存, 而实际的内存大小必须是 8 的整数倍
,18向上取整到24,主要是因为24是8的整数倍
,所以sizeof(struct1) 的结果是 24
结构体嵌套结构体
定义一个嵌套结构体 struct2_1_1
struct Struct2_1_1 {
// 占字节数 起始位置 占的位置
int c; // 4 0 (0 1 2 3)
double a; // 8 4 (8 9 10 11 12 13 14 15)
struct Struct3 struct3; // 8 (16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39) ==> struct3 内存大小 24
char b; // 1 40 (40)
short d; // 2 41 (42 43)
}struct2_1_1; // => 48
分析过程:
-
变量c
是int
类型 , 占用4字节, 从0开始, 占用位置0 1 2 3
-
变量a
是double
类型, 占用8字节,从4开始,占用位置8 9 10 11 12 13 14 15
-
struct3
结构体,上面分析占用24字节,最大属性内存占8字节(struct3 中的 a 是double类型,占8字节 )
, 依据内存对齐原则,当前成员的开始位置
应该能整除8,所以从 16开始,占用位置16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
-
变量b
是char
类型, 占用1字节,从40开始, 占用位置40
-
变量d
是short
类型, 占用2字节,从41开始, 占用位置42 43
因此struct2_1_1
需要的内存大小44
字节, 而 struct2_1_1
中的最大属性占用8字节
, 根据内存对齐原则可以 得出 结构体的内存大小必须是8的整数倍,即sizeof(struct2_1_1) = 48
内存分析图:
image.png注:空白部分为补齐的内存位置
网友评论