引言
内存对齐是内存里面一个很重要的词汇,可是大部分开发者对这个词汇的含义都是一知半解。
WJPerson*wj = [WJPerson alloc];
wj.name = @"无极";
wj.age = 30;
NSLog(@"对象类型的内存大小:%lu",sizeof(wj));
NSLog(@"对象实际的内存大小:%lu",class_getInstanceSize([wj class]));
NSLog(@"对象分配的内存大小:%lu",malloc_size((__bridge const void *)(wj)));
NSLog(@"-----------------------------------------");
WJPerson*wj2;
NSLog(@"对象类型的内存大小:%lu",sizeof(wj2));
NSLog(@"对象实际的内存大小:%lu",class_getInstanceSize([wj2 class]));
NSLog(@"对象分配的内存大小:%lu",malloc_size((__bridge const void *)(wj2)));
输出结果:
2021-06-16 13:12:08.712404+0800 内存对齐[3440:72350] 对象类型的内存大小:8
2021-06-16 13:12:08.712537+0800 内存对齐[3440:72350] 对象实际的内存大小:24
2021-06-16 13:12:08.712659+0800 内存对齐[3440:72350] 对象分配的内存大小:32
2021-06-16 13:12:08.712746+0800 内存对齐[3440:72350] -----------------------------------------
2021-06-16 13:12:08.712831+0800 内存对齐 [3440:72350] 对象类型的内存大小:8
2021-06-16 13:12:08.712923+0800 内存对齐[3440:72350] 对象实际的内存大小:0
2021-06-16 13:12:08.713007+0800 内存对齐[3440:72350] 对象分配的内存大小:0
结果分析:
-
sizeof:对象类型的内存大小,sizeof是用来计算一个变量或者一个常量、一种数据类型所占的内存字节数。自定义对象的本质是结构体指针,所以占8个字节。 -
class_getInstanceSize:对象实际(对齐后)的内存大小,内存大小是由类的成员变量的大小决定的。实际上并不是严格意义上的对象的内存的大小,因为内存进行了8字节对齐,所以wj的内存大小是24而不是20。而wj2只是声明变量,并没有走alloc方法开辟内存,所以大小是0。核心内存大小算法是:define WORD_MASK 7UL ((x + WORD_MASK) & ~WORD_MASK -
malloc_size:系统实际分配的内存大小,以16字节对齐,不足16的自动补齐。注意:系统的16字节对齐是在实际的内存大小(经过8字节对齐后)的基础上。上面的wj对象实际内存大小24字节,不是16的倍数,所以系统实际分配为32。
问题:
class_getInstanceSize和malloc_size底层做了什么?我们如何知道class_getInstanceSize是8字节对齐,而malloc_size是16字节对齐?
在研究后面重点之前,我们先来看下基本数据类型在arm64环境下占用的内存大小。

基本数据类型所占字节数.gif
下面解释为什么计算机会有内存对齐的概念,出于什么目的要内存对齐。
- 内存是以
字节为基本单位,cpu在读取数据时,是以块为单位读取,并不是以字节为单位读取。频繁读取未对齐的数据,会加大cpu的开销。字节对齐后,会降低cpu的存取次数,这种以空间换时间的做法降低了cpu的开销。 -
cpu存取:是以块为单位,存取未对齐的数据可能开始在上一个内存块,结束在另一个内存块。这样中间可能要经过复杂的运算在合并在一起,降低了效率,字节对齐后,提高了cpu的访问效率。
内存对齐规则:
数据成员对齐规则:
结构体(struct)(或联合体(union))的数据成员,第一个数据成员放在offset为0的地方(即首地址的位置),以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int为4字节),则要从4的整数倍地址开始存储。
结构体作为成员变量:如果一个结构体里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍开始存储(struct a里有struct b,b里有char,int,double等元素,那b应该从8(doudle为8 )的整数倍开始存储)
下面我们先来看一个例子:

struct内存.gif
struct WJPerson1{
double a;
char b;
int c;
short d;
}myPerson1;
struct WJPerson2
{
double b;
int c;
char a;
short d;
}myPerson2;
NSLog(@"%lu-%lu",sizeof(myPerson1),sizeof(myPerson2));
输出结果:
2021-06-16 16:13:21.621334+0800 内存对齐[4074:158189] 24-16
从上面我们可以看出,myPerson1和myPerson2两个结构体里面元素是一样的,只是顺序不同,内存大小却不一样,为什么?这就是结构体内存对齐。
具体分析如下:[p,q] p表示当前开始的位置,q表示大小
myPerson1:
- double a:
[0,7]即(0~7存放a) - char b:
[8,1]即(8存放b) - int c:
[12,15]即(9,10,11不是int 4得出倍数,废弃,12~15存放c) - short d:
[16,2]即(16 ~ 17存放d)
myPerson2:
- double a:
(0,7)即(0~7存放a) - int b:
(8,4)即(8~11存放b) - char c:
(12,1)即(12存放c) - short d:
(14,2)即(13不是d( short 2的倍数)位置废弃,14 ~ 15存放d)
下面这个是嵌套的结构体
struct WJPerson3 {
double a;
int b;
char c;
short d;
int e;
struct WJPerson1 str;
} myPerson3;

struct嵌套内存.gif
myPerson3具体分析如下:
- double a:
[0,7]即(0~7存放a) - char b:
[8,1]即(8存放b) - int c:
[12,15]即(9,10,11不是int 4得出倍数,废弃,12~15存放c) - short d:
[16,2]即(16 ~ 17存放d) - int e:
(20,4)即(18,19不是int 4得出倍数,废弃.20 ~ 23存放d) - 变量str: str是结构体变量,内存对齐原则
结构体成员要从其内部最大元素大小的整数倍地址开始存储。WJPerson1 中的最大的变量a( double)占8字节,所以offset从24开始,WJPerson1的内存大小是18字节。[24,18],即24 ~ 42存放 str,计算出来的是42个字节,但是myPerson3中最大的变量是str和 a都是8字节,所以myPerson3的实际内存大小必须是8的整数倍,42不是8的整数倍,因此补齐应该是48.