iOS 底层探究:内存对齐2

266 阅读3分钟

这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战 上文我们俩节了内存对齐的一些内容,今天来继续学习。

对齐原理分析

已知系统会根据数据类型跳过部分内存,那跳过的部分为什么不能存储数据?

25092736-d3c3d3cb444db5e0.png

如上图所示,对于不优化连续存储的情况,CPU读取8~15的内存数据,需要先读取1字节再读取4字节,CPU对于要读取的数据大小是有变化的。而优化后CPU先读取4字节(由于白色3字节空白所以可以直接读取4字节)再读取4字节再这段内存中是没有变化的。相比于第一种情况,优化后CPU要进行的操作变少了,这就实现了通过空间换取时间

系统内存开辟

分析了内存对齐原理,下面我们来看一下系统是如何开辟内存的。

1. 案例

PDObject定义如下:

@interface PDObject : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;

@end

调用:

#import "PDObject.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>

PDObject *pdObj = [PDObject alloc];
pdObj.name = @"HotpotCat";
pdObj.age = 18;

NSLog(@"sizeof:%zu class_getInstanceSize:%zu malloc:%zu",sizeof(pdObj),class_getInstanceSize([PDObject class]),malloc_size((__bridge const void *)(pdObj)));

那么sizeof、class_getInstanceSize、malloc_size分别输出多少呢? 验证:

sizeof:8 class_getInstanceSize:40 malloc:48
  • pdObj是一个结构体指针sizeof返回8
  • class_getInstanceSize由于存在isa8字节对齐所以返回40 。
  • malloc_size为什么返回48呢?

在系统的内存堆区中对象的内存是16字节对齐,成员变量是以8字节对齐(结构体内部)。对象与对象是16字节对齐。

image.png

2.为什么以16字节对齐?

为什么对象不以8字节对齐?而以16字节对齐?

image.png 假如一个对象内部成员变量都是8字节大小。

  • 以8字节对齐,内部没有多余空间,更容易发生访问错误。
  • 以16字节对齐内部有多余空间,不容易发生访问错误。 对于64字节的空间:
16 32 48 64
8 16 24 32 40 48 56 64

以8字节对齐需要访问8次,以16字节对齐需要访问4次

  • 明显以16字节对齐访问对象和成员变量碰到一起的概率小了。16字节对齐4次,8字节对齐8次。
  • 任何对象都继承自NSObject,但是很少有对象只有一个isa。所以最小的对象都应该是16。
  • 如果用32字节对齐呢?很明显空间浪费太大了。

成员变量字节对齐是8字节对齐,对象的内存对齐市16字节对齐

总结

  • 结构体对齐(三个原则)
    • 三个原则
      • 数据成员对齐规则:从成员大小或者成员的子成员大小的整数倍开始。
      • 结构体作为成员:从内部成员最大元素的整数倍地址开始存储。
      • 补齐:必须是内部最大成员的整数倍,不足的要补齐。
    • 对齐原理:优化CPU读取速度,以空间换时间。
    • 结构体嵌套补齐从内部开始补齐。
  • 内存大小获取
    • sizeof:是运算符,不是函数。获取对象的长度(对象本身)。
    • class_getInstanceSize:获取类的实例所占用的内存大小。大小只与成员变量有关。
    • malloc_size:alloc中实际开辟的空间。
  • calloc 16字节对齐,最小返回16.
    • 最终分配的内存大小逻辑在segregated_size_to_fit中。以16字节对齐向上取整
    • 为什么以16字节对齐?减少访问错误。