结构体内存对齐

479 阅读6分钟

计算需要开辟内存大小的底层代码

  1. 从objc源码探索alloc的过程中,有一段核心代码里面有关于需要申请开辟内存大小的代码,如下图: instanceSize.png

  2. 下面我们来看下fastInstanceSize这个函数,代码图如下: fastInstanceSize.png 函数里面的size此时是32,它是_flags与上FAST_CACHE_ALLOC_MASK来的,_flagspo出来是32800=0x8020,FAST_CACHE_ALLOC_MASK是0x1ff8,1000 0000 0010 0000 & 0001 1111 1111 1000 = 0000 0000 0010 0000 = 32。FAST_CACHE_ALLOC_DELTA16是8,size + extra - FAST_CACHE_ALLOC_DELTA16 = 32+0-8 = 24。

  3. 而我们的OCPeople类中只有两个属性,计算一下:NSString是8字节+int是4字节+isa是8字节等于20,如下图: OCPeople.png

  4. 我们再看下align16这个函数,代码如下图:这个函数也是16字节对齐算法,(24+15)& ~15 = 00100111 & ~00001111 = 0010 0000 = 32,16的整数倍。这里return的就是32,代码运行,一路返回。 align16.png

  5. 回到上面的instanceSize函数,找到调用这个函数的地方,就到了_class_createInstanceFromZone这个函数,这里return回来的32就赋值给了size,我们能看到需要向系统申请开辟内存空间的size大小为32。 _class_createInstanceFromZone.png

到这里就有一个疑问了,OCPeople类两个属性的时候我们计算是占20个字节,为什么要开辟32字节大小的内存空间呢?

影响对象内存大小的因素

  1. 对象所占内存大小与什么有关呢?不妨先给OCPeople类多添加几个属性: OCPeople40.png 打印输出此时的大小为40: 40.png

  2. 我们再注释掉几个属性: OCPeople16.png 打印输出此时的大小为16: 16.png 根据上面的两次打印,从40变成了16,可以看出属性是会影响InstanceSize的。

  3. 那如果给OCPeople类添加成员变量是否会有影响呢?添加一个成员变量试试: OCPeople24.png 打印输出此时的大小为24: 24.png 从16变成了24,可以看出成员变量也是会影响InstanceSize的。

  4. 那方法是否也会影响这个InstanceSize呢?来添加个方法看看: OCPeople方法声明.png OCPeople方法实现.png 打印输出此时的InstanceSize还是24,没有变化: OCPeople方法24.png 那说明方法对这个InstanceSize是没有影响的。

  5. 类方法会不会影响呢?如下图:

类方法声明.png 类方法实现.png 打印输出如下图:InstanceSize为24,没有变化,跟刚才一样。 输出类方法size.png

上面一系列探索可以看出属性和成员变量会影响对象的内存大小,方法不会影响对象的内存大小,方法没有存到对象的内存中的。至于类方法,它也不会影响对象的内存大小。

不同类型属性在内存中的存放

  1. 接下来我们看下不同类型的属性在对象的内存中的存放,打开所有的属性: OCPeople属性.png

  2. 给实例对象的属性赋值: OCPeople属性赋值.png

  3. 打印输出如下图: po.png 3.1 x/8gx中前面的x表示内存读取并打印,8代表显示8个内存单元,g表示每个地址单元的长度为8字节,后面的x表示按16进制显示变量。

3.2 heightdouble类型的,它的输出显示要用p/f,表示十六进制打印输出浮点类型,也可以用e -f f --输出。

3.3 agec1c2的值是存在一个内存单元中的,其他属性值都是存在不同的内存单元中,这样的存放其实涉及到一个原则:内存对齐原则。

  1. 内存对齐原则: 4.1 数据成员对齐规则:结构体(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始)存储。min(当前开始的位置m n) m=9 n=4 //9 10 11 12

4.2 结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a 里存有struct b,b里有charintdouble等元素,那b应该从8的整数倍开始存储。)

4.3 收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。

结构体的内存对齐

现在我们看下结构体的内存对齐,代码如下已有相应的解释: 结构体内存.png 为什么NSLog(@"%lu--%lu",sizeof(struct1),sizeof(struct2));输出是24--16 的解释全在上面的图中已说明。

关于结构体struct3的存储说明如下图: struct3.png 结构体struct1、struct2、struct3的大小打印输出如下: postruct3.png

关于结构体内部嵌套结构体的用法计算大小的时候,需要注意:如果struct a 里存有intcharstruct b等元素,b里有charintdouble等元素,那struct a 的总大小是其内部最大成员的整数倍,这个内部最大成员的比较也要包括struct b的内部成员,此时是按照8的整数倍开始存储。如下图:

最大成员为8,在内部嵌套的结构体OCStruct2 str里,外部结构体struct4的大小是8的倍数,为32: struct4.png poStruct4.png

结构体struct5的大小是其内部最大成员的整数倍,不足的要补齐,内部最大成员是4,4的倍数,为12。 结构体struct6中最大成员为8,在外部结构体struct6里,外部结构体struct6的大小是8的倍数,为32。 结构体struct7的最大成员是4,内部嵌套的结构体OCStruct5 str里的最大成员也是4,计算外部结构体struct7的大小是4的倍数,为24。如下图 567.png po567.png

后面随意测试了下,如下图: 891011.png po891011.png

关于结构体的内存对齐暂时研究到此,有错误欢迎指正!

注:

  1. NSLog(@"%lu",sizeof(ocp));结果是8,ocp是一个对象,对象的本质是指针地址,一个地址是占8字节,对象=结构体指针,故打印输出是8.
  2. NSLog(@"%lu",class_getInstanceSize(OCPeople.class));它的输出结果除了要包括所有的成员变量所占类型字节数,还要包括isa,然后遵循8字节对齐。
  3. NSLog(@"%lu",malloc_size((__bridge const void *)ocp));它是系统实际分配的内存大小,遵循16字节内存对齐。