引言
上一篇文章,探究 alloc的过程中,提到内存对齐。下面我们通过对OC对象QLPerson探究来展开内存对齐的探究。

OC对象本质

我们探究oc对象底层的本质,通过如下方式,讲.m文件转为.cpp文件。参考我的这篇文章
QLPerson.h 代码如下:
002-OC对象原理探究 - 结构体内存对齐 - 图1
QLPerson.m 如下:
002-OC对象原理探究 - 结构体内存对齐 - 图2
我通过xcrun来转成cpp文件

  1. xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc QLPerson.m -o QLPerson.cpp

QLPerson.cpp拖入xcode,compile sources去掉QLPerson.cpp,使其不参与编译。在QLPerson.cpp中搜索QLPerson_找到底层实现为结构体QLPerson_IMPL
002-OC对象原理探究 - 结构体内存对齐 - 图3
其中结构体NSObject_IMPL

  1. struct NSObject_IMPL {
  2. Class isa;
  3. };

由此可得出OC对象的本质是结构体,引出我今天将要探究的结构体内存对齐

结构体内存对齐探究:普通成员变量

定义结构体代码如下:

  1. struct QLStruct1 {
  2. double a; // 8
  3. char b; // 1
  4. int c; // 4
  5. short d; // 2
  6. } struct1;
  7. 其中每个成员变量后面的注释为类型所占字节大小

那么struct1的内存到底占多少呢?我们这里用sizeof()来计算结果:

  1. NSLog(@"%lu",sizeof(struct1));

打印结果如下:002-OC对象原理探究 - 结构体内存对齐 - 图4

这24字节是怎么得到的?
首先,我们要了解,结构体对齐有如下原则:

  1. 1、数据成员对齐规则:结构(struct)或union的数据成员,第一个数据的成员,放在offset0 的地方,以后每个数据成员存储的起始位置,要从该成员大小成员的子成员大小(只要该成员有子成员,数组、结构体等)的整数倍开始。
  2. 2、结构体作为成员:如果一个结构里,有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。
  3. 3、结构体的总大小,也就是sizeOf的结果,必须是其内部成员的整数倍,不足的要补齐

根据内存对齐原则,得到下图是struct1的成员变量的分布情况(excel画的)002-OC对象原理探究 - 结构体内存对齐 - 图5代码如下:

  1. struct QLStruct1 {
  2. double a; // 8 0 1 2 3 4 5 6 7
  3. char b; // 1 8
  4. int c; // 4 (9,10,11) 12 13 14 15
  5. short d; // 2 16 17
  6. } struct1; // 总计:成员变量总和为17,收尾--最大成员变量double(8)的倍数 --> 24
  7. 小括号为跳过

9,10,11为什么会空出来,因为struct1的最大成员变量为double,也就是最大成员变量的大小为8字节,根据原则1,可知该结构体按8字节对齐。char b排到8后,int c本应该从9开始排,但是9不能被8整除,因此往后移到12开始分配4字节int c
short d排布结束后,到了17,根据原则3struct1按照8字节内存对齐,不足的需要补齐,因此,最终sizeof(struct1)得到的是24而不是17

结构体内存对齐探究:结构体作为成员变量

  1. struct QLStruct3 {
  2. double a; // 8 0-7
  3. int b; // 4 8 9 10 11
  4. char c; // 1 12
  5. short d; // 2 (13)14 15
  6. int e; // 4 16 17 18 19
  7. struct QLStruct1 stru; // 17 (20,21,22,23)24 25 26 27 ... 40
  8. } struct3; // 总计:成员变量总和为40,收尾--最大成员变量double(8)的倍数 --> 48
  9. 小括号为跳过

根据探究普通成员变量的方式,根据原则2得到sizeof(struct3)的大小为48

总结
结构体内存对齐,要熟记以上3个原则,则面对复杂的结构体,也能得心应手的处理了。