内存优化
看完了结构体的内存对齐,我们再来看一下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关联类。敬请期待~