美文网首页
OC底层原理三:alloc流程

OC底层原理三:alloc流程

作者: 赤耳诺 | 来源:发表于2020-09-21 17:55 被阅读0次

    oc底层原理一里我们探索了如何定位底层源码的三种方式,在oc底层原理二里我们配置了objc4-781 的可编译环境,让我们更直观的探索底层流程。

    可直接下载编译成功的objc4-781

    本文就在以上两篇的基础上,探究下alloc的实现流程。

    首先打开配置好的objc,利用符号断点的方式走一下alloc的整个流程,流程图如下:

    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指针,指向这个对象,然后绑定这个对象。具体的绑定过程后面抽一篇具体研究。

    相关文章

      网友评论

          本文标题:OC底层原理三:alloc流程

          本文链接:https://www.haomeiwen.com/subject/bxkaektx.html