OC底層原理三:alloc流程

oc底層原理一里我們探索了如何定位底層源碼的三種方式,在oc底層原理二里我們配置了objc4-781 的可編譯環(huán)境,讓我們更直觀的探索底層流程。

可直接下載編譯成功的objc4-781

本文就在以上兩篇的基礎(chǔ)上,探究下alloc的實(shí)現(xiàn)流程。

首先打開(kāi)配置好的objc,利用符號(hào)斷點(diǎn)的方式走一下alloc的整個(gè)流程,流程圖如下:

alloc流程圖

一步步走下來(lái),我們會(huì)發(fā)現(xiàn)_class_createInstanceFromZone方法是alloc流程的核心實(shí)現(xiàn):

_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
    //讀取類(lèi)的信息
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;
    //詢問(wèn)需要開(kāi)辟的內(nèi)存大小,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;
    }
    //將類(lèi)和isa指針關(guān)聯(lián)
    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方法創(chuàng)建實(shí)例內(nèi)存空間,主要有三部分實(shí)現(xiàn):
- cls->instanceSize:計(jì)算需要開(kāi)辟的內(nèi)存空間大小
- calloc:申請(qǐng)內(nèi)存,返回地址指針
- obj->initInstanceIsa:將類(lèi)與isa關(guān)聯(lián)

alloc核心方法

instanceSize源碼
size_t instanceSize(size_t extraBytes) const {
        //快速計(jì)算內(nèi)存大小
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }
        //計(jì)算isa和成員變量需要的內(nèi)存大小
        size_t size = alignedInstanceSize() + extraBytes;
        //如果不足16字節(jié)的,補(bǔ)齊16字節(jié)
        if (size < 16) size = 16;
        return size;
    }

首先就是有個(gè)fastpath的判斷,判斷是否需要編譯器優(yōu)化。根據(jù)調(diào)試可以從緩存中讀取所需開(kāi)辟空間大小cache.fastInstanceSize

size_t fastInstanceSize(size_t extra) const
    {
        ASSERT(hasFastInstanceSize(extra));
        //判斷extra是否在編譯時(shí)就可以確定其為常量,如果extra為常量,該函數(shù)返回1,否則返回0。
        //如果extra為常量,可以在代碼中做一些優(yōu)化來(lái)減少處理extra的復(fù)雜度。
        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
            //十六字節(jié)對(duì)齊
            return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
        }
    }

builtin函數(shù)是GCC提供的,可以實(shí)現(xiàn)一些簡(jiǎn)單快捷的功能來(lái)方便程序編寫(xiě),另外,很多builtin函數(shù)可用來(lái)優(yōu)化編譯結(jié)果。
__builtin_constant_p (extra):判斷extra是否在編譯時(shí)就可以確定其為常量,如果extra為常量,該函數(shù)返回1,否則返回0。如果extra為常量,可以在代碼中做一些優(yōu)化來(lái)減少處理extra的復(fù)雜度。

align16(size + extra - FAST_CACHE_ALLOC_DELTA16)通過(guò)字面就可以理解16字節(jié)對(duì)齊,看下源碼:

static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}

size_t獲取sizeof計(jì)算的數(shù)據(jù)類(lèi)型,保存的一般是個(gè)整數(shù)。

我們以align(8)為例看下16字節(jié)對(duì)齊算法:
實(shí)際算法為(8 + size_t(15)) & ~size_t(15),即(8+15)&~15,即23&~15,二進(jìn)制表示0x 0001 0111 & 0x 1111 0000,與運(yùn)算相同的為1,不同的為0,所以我們知道這其實(shí)就是對(duì)16位以下清零的操作,最終計(jì)算為0x 0001 0000,即16。

為什么要16字節(jié)對(duì)齊呢?

我們都知道內(nèi)存是由一個(gè)個(gè)字節(jié)組成的,但cpu在讀取的時(shí)候不會(huì)按照字節(jié)去讀取,cpu把內(nèi)存當(dāng)成是一塊一塊的,塊的大小可以是2,4,8,16 個(gè)字節(jié),因此CPU在讀取內(nèi)存的時(shí)候是一塊一塊進(jìn)行讀取的,塊的大小稱(chēng)為內(nèi)存讀取粒度。

假設(shè)CPU要讀取一個(gè)8字節(jié)大小的數(shù)據(jù)到寄存器中,如果數(shù)據(jù)從0字節(jié)開(kāi)始,直接將0-7四個(gè)字節(jié)完全讀取到寄存器。但如果數(shù)據(jù)從1字節(jié)開(kāi)始讀取,首先先將0-7字節(jié)讀到寄存器,并再次讀取8-15字節(jié)的數(shù)據(jù)進(jìn)寄存器,接著把0字節(jié),9-15字節(jié)的數(shù)據(jù)剔除,最后合并1-8字節(jié)的數(shù)據(jù)進(jìn)寄存器,做了這么多額外操作,大大降低了CPU的性能。

而對(duì)齊后則以空間換時(shí)間,大大提高了cpu的讀取速度。

而我們知道每個(gè)對(duì)象都有個(gè)isa指針,isa指針占用8個(gè)字節(jié),每一個(gè)屬性也是占用8個(gè)字節(jié)。當(dāng)無(wú)屬性的時(shí)候每個(gè)對(duì)象最少占8個(gè)字節(jié)。如果用8字節(jié)對(duì)齊的話,對(duì)象和對(duì)象之間的isa就會(huì)連在一塊,可能訪問(wèn)時(shí)就會(huì)出現(xiàn)問(wèn)題。所以16字節(jié)對(duì)齊一方面解決了這樣的隱患,而且也方便了以后的擴(kuò)展。

calloc源碼
void    *calloc(size_t __count, size_t __size) __result_use_check __alloc_size(1,2);

calloc在內(nèi)存的動(dòng)態(tài)存儲(chǔ)區(qū)中分配count個(gè)長(zhǎng)度為size的連續(xù)空間。num:屬性個(gè)數(shù)+isa,size:instanceSize計(jì)算出的size。
分配好內(nèi)存后,返回地址指針。

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初始化一個(gè)isa指針,指向這個(gè)對(duì)象,然后綁定這個(gè)對(duì)象。具體的綁定過(guò)程后面抽一篇具體研究。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容