先抛问题
各位看官请看下面两种case的输出分别是多少:
case1:
|
|
case2:
|
|
可以先分析一下打印情况,我这里直接上结果:
|
|
这就有点意思了,class_getInstanceSize获取到的大小并不完全和对象的真实大小完全一致,通过对相关源码的探究,最终发现了原因,这里先上结论:
class_getInstanceSize获取的大小是根据8字节对齐计算,而内存的实际分配策略是根据16字节对齐计算
源码分析
我们首先来看看runtime中class_getInstanceSize的实现,通过调用栈的追踪,最终定位到下面几个比较重要的函数:
|
|
通过以上几个函数的调用,就可以验证上面的结论class_getInstanceSize是以8字节对齐的,
在上面的函数中,我们还发现了一个很熟悉的身影,就是下面这个结构:
|
|
我们知道,这里面的数据都是只读数据,是在程序编译期就决定了的,我们常用的实例变量就存放在这个结构体中,这也是为什么类在初始化后不能再动态添加实例变量的原因。
然后我们再来看看类的创建过程,还是从源码入手,不过内存创建的函数调用栈比较复杂,我画个简单的流程图来表示下先:
在runtime库中,这个调用堆栈跟到最后两个标黄部分就进不去了,不过我们还是通过一些细节找到了这两个函数的出处,并最终定位到了负责内存分配过程中size计算的函数,该部分代码位于libsystem_malloc库中,具体代码实现如下:
|
|
通过以上源码也证明了上面的结论内存的实际分配策略是根据16字节对齐计算
补充部分
因为iOS中的类本质上都是结构体,所以他们的内存大小应该也需要满足c语言中结构体对齐的原则,这里简单介绍下c语言中如何进行字节对齐:
内部对齐
即结构体内部变量的起始地址需要是变量自身大小的倍数。具体对齐逻辑参考下图:
外部对齐
即结构体的整体大小需要和当前运行环境的字对齐。
这里还有一个知识点需要提及,为了更高效的利用内存,xcode编译器会对实例变量进行重排序,这个在我上面的LLYModel中并未体现,感兴趣的同学可以自行实验
小结
以上就是这次对iOS类分配内存大小探索的全部内容,首先是发现问题,然后通过源码的分析找到问题产生的原因,中间的探索过程一度中断,不过最后还是通过一些小的线索定位到问题的所在,在源码分析过程中,我们应该做到抓住关键问题,忽略干扰条件,多一些细心和耐心。