在oc底层原理一里我们探索了如何定位底层源码的三种方式,在oc底层原理二里我们配置了objc4-781 的可编译环境,让我们更直观的探索底层流程。
可直接下载编译成功的objc4-781
本文就在以上两篇的基础上,探究下alloc的实现流程。
首先打开配置好的objc,利用符号断点的方式走一下alloc的整个流程,流程图如下:
一步步走下来,我们会发现
_class_createInstanceFromZone
方法是alloc流程的核心实现:
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
//读取类的信息
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
//询问需要开辟的内存大小,extraBytes为0
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
//将类和isa指针关联
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
_class_createInstanceFromZone
方法创建实例内存空间,主要有三部分实现:
- cls->instanceSize
:计算需要开辟的内存空间大小
- calloc
:申请内存,返回地址指针
- obj->initInstanceIsa
:将类与isa
关联
alloc核心方法
instanceSize源码
size_t instanceSize(size_t extraBytes) const {
//快速计算内存大小
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
//计算isa和成员变量需要的内存大小
size_t size = alignedInstanceSize() + extraBytes;
//如果不足16字节的,补齐16字节
if (size < 16) size = 16;
return size;
}
首先就是有个fastpath
的判断,判断是否需要编译器优化。根据调试可以从缓存中读取所需开辟空间大小cache.fastInstanceSize
。
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
//判断extra是否在编译时就可以确定其为常量,如果extra为常量,该函数返回1,否则返回0。
//如果extra为常量,可以在代码中做一些优化来减少处理extra的复杂度。
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
} else {
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
// remove the FAST_CACHE_ALLOC_DELTA16 that was added
// by setFastInstanceSize
//十六字节对齐
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
builtin函数是GCC提供的,可以实现一些简单快捷的功能来方便程序编写,另外,很多builtin函数可用来优化编译结果。
__builtin_constant_p (extra)
:判断extra是否在编译时就可以确定其为常量,如果extra为常量,该函数返回1,否则返回0。如果extra为常量,可以在代码中做一些优化来减少处理extra的复杂度。
align16(size + extra - FAST_CACHE_ALLOC_DELTA16)
通过字面就可以理解16字节对齐,看下源码:
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
size_t
获取sizeof
计算的数据类型,保存的一般是个整数。
我们以align(8)为例看下16字节对齐算法:
实际算法为(8 + size_t(15)) & ~size_t(15)
,即(8+15)&~15
,即23&~15
,二进制表示0x 0001 0111 & 0x 1111 0000
,与运算相同的为1,不同的为0,所以我们知道这其实就是对16位以下清零的操作,最终计算为0x 0001 0000
,即16。
为什么要16字节对齐呢?
我们都知道内存是由一个个字节组成的,但cpu在读取的时候不会按照字节去读取,cpu把内存当成是一块一块的,块的大小可以是2,4,8,16 个字节,因此CPU在读取内存的时候是一块一块进行读取的,块的大小称为内存读取粒度。
假设CPU要读取一个8字节大小的数据到寄存器中,如果数据从0字节开始,直接将0-7四个字节完全读取到寄存器。但如果数据从1字节开始读取,首先先将0-7字节读到寄存器,并再次读取8-15字节的数据进寄存器,接着把0字节,9-15字节的数据剔除,最后合并1-8字节的数据进寄存器,做了这么多额外操作,大大降低了CPU的性能。
而对齐后则以空间换时间,大大提高了cpu的读取速度。
而我们知道每个对象都有个isa
指针,isa
指针占用8个字节,每一个属性也是占用8个字节。当无属性的时候每个对象最少占8个字节。如果用8字节对齐的话,对象和对象之间的isa
就会连在一块,可能访问时就会出现问题。所以16字节对齐一方面解决了这样的隐患,而且也方便了以后的扩展。
calloc源码
void *calloc(size_t __count, size_t __size) __result_use_check __alloc_size(1,2);
calloc
在内存的动态存储区中分配count个长度为size的连续空间。num:属性个数+isa,size:instanceSize
计算出的size。
分配好内存后,返回地址指针。
initIsa源码
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
obj->initInstanceIsa
初始化一个isa指针,指向这个对象,然后绑定这个对象。具体的绑定过程后面抽一篇具体研究。