二、alloc的流程

在进行源码跟踪之前先了解一下以下的三个方法:

sizeof:判断数据类型或者表达式长度的运算符,返回一个变量或者类型的大小(以字节为单位)

class_getInstanceSize:返回类实例的大小。

malloc_size:系统分配的内存大小

sizeofclass_getInstanceSize 是获得这个对象需要占用的内存。malloc_size 是获得系统会给这个实例对象分配的内存。

通过打印,可以知道 sizeofclass_getInstanceSizemalloc_size 打印的结果是不一样的。

为什么?来看一下到底怎么回事。

alloc 源码跟踪:

我们打开可编译的源码,对alloc跟踪,大致的流程是这样的:alloc -> _objc_rootAlloc -> callAlloc -> _objc_rootAllocWithZone -> _class_createInstanceFromZone,但在运行的时候,alloc的流程调用方法并不完全是这样,后面会有解释!

它的核心方法是_class_createInstanceFromZone,来看一下源码的实现:

这个方法共有三个核心步骤,先来看看第一个。

1. instanceSize -> 计算对象需要开辟的内存。

先来看一下instanceSize的源码实现:

大致可以分两个步骤:

  1. 缓存中快速计算内存大小: cache.fastInstanceSize
  2. 计算 isa 和成员变量需要的内存大小(alignedInstanceSize),如果不足16字节的,补齐16字节

fastInstanceSize 的源码实现:

可以看到,在最后,会调用一个 align16 的函数,来看一下这个函数的实现:

发现,这是一个内联函数,并且,它的作用就是,传一个size_t,通过size_t的大小,计算并返回size_t 16字节对齐后的大小。

为什么是16字节对齐,其实是可以算出来的,假设参数 x = 8,就变成了 (8 + size_t(15)) & ~size_t(15),~有取反的作用,这个值在二进制中的0变成1,1变成0的意思。具体的算法看下图:

通过这张图可以得知,align16 就是进行16字节对齐的一个函数。

alignedInstanceSize 的源码调用
看一下在源码中 alignedInstanceSize 干了什么:

可以看到,alignedInstanceSize就是拿到类的成员变量的大小,进行word_alignword_align是什么意思呢?来看一下源码实现:

说白了,alignedInstanceSize的最终目的就是为了拿到类的成员变量的大小进行8字节对齐,为什么是8字节对齐呢?其实和align16的原理一样。

这也是为什么在下面会有不满16字节,补齐16字节的判断。

另外,instanceSize方法只是计算出当前类开辟内存时需要的大小,不能决定在运行时当前类所占用的内存就是这个大小,决定当前类在运行时占用的内存大小由 calloc 函数决定。下面,来看一下这个calloc

2. calloc -> 开辟内存。

在 libmalloc-317.40.8 源码中,calloc的流程:

calloc -> _malloc_zone_calloc -> default_zone_calloc -> nano_calloc -> _nano_malloc_check_clear -> segregated_size_to_fit
直到 segregated_size_to_fit 方法中,查看 NANO_REGIME_QUANTA_SIZE 的定义发现以下宏定义:

     #define SHIFT_NANO_QUANTUM        4
     #define NANO_REGIME_QUANTA_SIZE    (1 << SHIFT_NANO_QUANTUM)    // 16
     #define NANO_QUANTA_MASK        (NANO_REGIME_QUANTA_SIZE - 1)
     #define NANO_SIZE_CLASSES        (NANO_MAX_SIZE/NANO_REGIME_QUANTA_SIZE)

可以得知,calloc 开辟内存是遵守 16 字节对齐的。什么意思呢,就是在instanceSize计算出当前类的内存大小后,由calloc对当前类的内存大小进行16字节对齐,这个才是系统分配给当前类在运行时真正占用的内存大小。

这就是前面提到的 sizeofclass_getInstanceSizemalloc_size 打印的结果是不一样的原因。

3. initInstanceIsa

initInstanceIsa最终会调用initIsa,来看一下initIsa的源码实现:

其实这个方法就是将 cls 和 isa 绑定在一起,进行关联。

posted @ 2021-11-29 20:15  Coder_张三  阅读(229)  评论(0)    收藏  举报