内存优化
看完了结构体的内存对齐,我们再来看一下OC对象的内存对齐又是怎样的呢?
JQPerson中自定义的变量和JQStruct2的成员的类型和顺序是一模模一样样的,他们打印出来的内存大小都是24字节,也是一模模一样样的,乍一看,没毛病呀。大哥,你忘记了对象本身自带了一个变量isa指针吗?它也占了8个字节呢。所以这样一看,JQPerson中自定义的变量只占了16个字节,这就很奇怪了啊,变量的类型和顺序都是一样的啊。这到底是为啥呢?这就是接下来我们要说的内存优化。
来,我们具体看看是它是怎么优化的,上代码:
通过lldb断点打印可以看出 :
isa的值通过读取0x0000000109dc6658(8字节)这个内存的数据;a的值通过读取0x4067200000000000(8字节)这个内存的数据;b的值通过读取0x00000012(4字节)这个内存的数据,c的值通过读取0x0002(2字节)这个内存的数据,d的值通过读取0x0063(2字节)(内存对齐,高位补0,所以这里看着就是俩字节了)这个内存的数据,
我们发现 int b,short c,char d,共用了一个8字节内存空间,系统进行了内存优化,将对象的属性或者变量存储顺序进行了重排,达到内存占用最小。
上面这个例子理解起来可能还不够透彻,下面我们在JQPerson中在增加一些属性,打印一下内存数据,一看就明白了。
看图👆,没有文字了!!!
由此我们可以得出结论:
- OC对象与对象之间是以16字节对齐的。
- 对象内部的成员变量之间是以8字节对齐的(OC最大数据类型是8字节,而每个对象都自带了一个8字节的
isa)。
calloc 开辟内存
上面我们讲到了instanceSize方法计算对象所需的内存大小,并且拓展了内存对齐和内存优化。接着,我们就来看一下calloc是怎么去开辟内存的。
好,我们断点快速来到_class_createInstanceFromZone方法中
这里可以看出,此时的obj还没有进行赋值,就已经有地址了,说明系统给obj分配了一块内存地址(脏地址)。
接着走断点,看到:执行calloc后打印的是一个16进制的指针地址,说明已经开辟了内存,但是和平常见到的地址指针(下图👇)不一样。
也就是说,calloc只是开辟了内存;但是这块内存空间并没有和相应的类进行关联。
我们一步一步来看,先点击calloc,进去看看calloc都做了什么?
发现这里进不去了,calloc没有提供方法实现,好想进去啊,怎么办?
拿出我们上篇文章讲的三种底层调试方法,符号断点跟一下流程
看到这里calloc方法在libsystem_malloc.dylib中,这是个系统库啊。毫不犹豫,直接去 Open Source 下载libsystem_malloc.dylib的源码啊。(ps:其实上面已经指出了calloc的位置,macOS 11.3/usr/include/malloc,天哪,这明显是个系统库啊)
好,接下里,我们拿malloc的源码编译调试,看看calloc的做了啥。上代码
- 进入
calloc
- 进入
_malloc_zone_calloc
33.png
- 进入
zone->calloc,发现点不进去,怎么办呢?首先想到的是汇编跟一下流程。但是注意,C语言指针->指向的就是函数的首地址,我们直接打印一下zone->calloc看看
- 看到下一个跳转是
default_zone_calloc,全局搜索default_zone_calloc
卧槽,又来一个zone->calloc
- 断点来到这里,继续打印一下
zone->calloc看看
- 看到下一个跳转是
nano_calloc,全局搜索nano_calloc
- 断点发现会执行到
_nano_malloc_check_clear,进入_nano_malloc_check_clear看一下
- 继续进入
segregated_size_to_fit看看
这里发现只有201、202这两句核心代码:
k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta slot_bytes = k << SHIFT_NANO_QUANTUM;
这两句啥意思呢?我们看到 >> 和 <<这两个符号,这很熟悉了啊,这不就是右移和左移嘛!
我们分别点击宏NANO_REGIME_QUANTA_SIZE和SHIFT_NANO_QUANTUM进去看一下都代表什么
知道了宏的定义,我们就还原一下上面201、202那两句代码:
k = (size + (1 << 4) - 1) >> 4; slot_bytes = k << 4;
这就很清晰了,前面内存对齐时我们讲过, >> 4 再 << 4 得出的结果就是16的整数倍,(size + (1 << 4) - 1) = size + 15。意思就是先给size升到16(二进制10000)以上,不足的位全抹0。这就是16进制对齐的算法!
我们拿上面传的40计算一下:
OK,到此,我们calloc的底层就结束了。
下面继续完善一下alloc的流程图:
总结
- 结构体是以其最大成员的字节数对齐的。
- 对象内部的成员变量之间是以8字节对齐的。
- OC对象与对象之间是以16字节对齐的。
instanceSize是计算实例对象所需要的内存大小calloc是系统为对象开辟内存
下一篇,我们来介绍OC对象的最后一个内容:isa关联类。敬请期待~




















