iOS底層原理_02:OC對(duì)象原理(中)

第二節(jié)課 OC對(duì)象原理(中)

底層LLVM優(yōu)化

上篇文章我們說到,實(shí)際代碼查看的流程是 alloc->_objc_rootAlloc->callAlloc->_objc_rootAllocWithZone,但是,我們通過斷點(diǎn)發(fā)現(xiàn)實(shí)際流程卻是alloc->objc_alloc->callAlloc->objc_msgSend->alloc->_objc_rootAlloc-callAlloc,這個(gè)到底是為啥呢?

原因是蘋果覺得alloc是比較特殊的方法,只要是alloc,就先走objc_alloc(類似hook),執(zhí)行了一些底層優(yōu)化、標(biāo)記,再執(zhí)行alloc方法,具體需要探索LLVM的源碼,這個(gè)我們后續(xù)再進(jìn)行補(bǔ)充。
LLVM下載地址

所以實(shí)際的alloc流程應(yīng)該為:
alloc->objc_alloc->LLVM底層優(yōu)化、標(biāo)記等等->objc_alloc->objc_msgSend->alloc->LLVM判斷標(biāo)記過->_objc_rootAlloc
這也是為什么callalloc走兩次的原因,所以我們將之前的流程圖再次補(bǔ)充一下。

alloc主線流程圖補(bǔ)充.png

對(duì)象的內(nèi)存的影響因素

上篇文章我們討論了一下字節(jié)對(duì)齊,字節(jié)對(duì)齊的最終又是以內(nèi)存的方式展現(xiàn),所以我們來探究一下能影響內(nèi)存的因素。
先看下我們上篇文章寫的例子


02-內(nèi)存影響因素.png

正常的class_getInstanceSize,是32,那我們干掉一些屬性后呢?發(fā)現(xiàn)有減少


02-屬性變量影響內(nèi)存.png

這證明我們的屬性是對(duì)內(nèi)存有影響的,那成員變量應(yīng)該也是一樣的,我們添加后發(fā)現(xiàn),確實(shí)影響了內(nèi)存大小。


02-成員變量影響內(nèi)存.png
02-成員變量影響內(nèi)存2.png

那么方法呢?添加了一個(gè)方法后發(fā)現(xiàn)并沒有變化,因?yàn)榉椒ú徽加脙?nèi)存,這個(gè)我們后續(xù)會(huì)再詳細(xì)進(jìn)行講解。對(duì)于內(nèi)存的理解我們可以先看下面的圖。


02-內(nèi)存畫圖舉例.png

Person通過alloc開辟了一塊堆的空間,外部通過對(duì)象的地址(棧里)來進(jìn)行指向。這個(gè)內(nèi)存空間里的就是Person里面的各項(xiàng)成員變量以及isa。

02-x:8gx輸出.png

我們新增幾條屬性后,通過x/8gx輸出看到,左邊0x60000336b6c0為首地址,對(duì)應(yīng)的第一個(gè)對(duì)象是0x0000000101968888,也就是isa。后面按順序排列的依次是各個(gè)屬性變量,每8字節(jié)一個(gè)對(duì)象,這也就是我們對(duì)齊原則。
需要注意的一點(diǎn)是我們的190.5是po不出來,我們可以使用e -f f-- 0x4067d00000000000,或者p/f 0x4067d00000000000.因?yàn)槲覀冋5膒o打印不出來,double與float類型需要單獨(dú)輸出打印

結(jié)構(gòu)體內(nèi)存對(duì)齊

剛才的例子中,我們發(fā)現(xiàn)第二個(gè)變量,0x0000001200006261實(shí)際上是3個(gè)變量組成的,0x12、0x62、0x61,這個(gè)是由于進(jìn)行了響應(yīng)的內(nèi)存對(duì)齊,那我們就來看看結(jié)構(gòu)體的內(nèi)存對(duì)齊原則

1:數(shù)據(jù)成員對(duì)齊規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第一個(gè)數(shù)據(jù)成員放在offset為0的地方,以后每個(gè)數(shù)據(jù)成員存儲(chǔ)的起始位置要從該成員大小或者成員的子成員大小(只要該成員有子成員,比如說是數(shù)組,結(jié)構(gòu)體等)的整數(shù)倍開始(比如int在32位機(jī)為4字節(jié),則要從4的整數(shù)倍地址開始存儲(chǔ)。

2:結(jié)構(gòu)體作為成員:如果一個(gè)結(jié)構(gòu)里有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從其內(nèi)部最大元素大小的整數(shù)倍地址開始存儲(chǔ).(struct a里存有struct b,b里有char,int ,double等元素,那b應(yīng)該從8的整數(shù)倍開始存儲(chǔ).)

3:收尾工作:結(jié)構(gòu)體的總大小,也就是sizeof的結(jié)果,.必須是其內(nèi)部最大成員的整數(shù)倍.不足的要補(bǔ)齊。

我們先來看兩個(gè)結(jié)構(gòu)體

struct HZMStruct1 {
    double a;      
    char b;        
    int c;          
    short d;        
}struct1;

struct HZMStruct2 {
    double a;       
    int b;          
    char c;        
    short d;        
}struct2;

這兩個(gè)結(jié)構(gòu)體的成員變量完全一樣,只是順序不一樣,這樣的兩個(gè)結(jié)構(gòu)體的sizeof會(huì)一樣嘛?直接上結(jié)果


02-結(jié)構(gòu)體的sizeof.png

Why?

    double a;       // 8字節(jié)    存儲(chǔ)位置[0 7]
    char b;         // 1字節(jié)    [8]
    int c;          // 4字節(jié)    (9 10 11 [12 13 14 15]不是整數(shù)倍數(shù)的位置pass掉 
    short d;        // 2字節(jié)    [16 17] 24
}struct1;

struct HZMStruct2 {
    double a;       // 8字節(jié)    [0 7]
    int b;          // 4字節(jié)    [8 9 10 11]
    char c;         // 1字節(jié)    [12]
    short d;        // 2字節(jié)    (13 [14 15] 16
}struct2;

在我們存儲(chǔ)的過程中,其實(shí)蘋果會(huì)自動(dòng)幫我們進(jìn)行最優(yōu)化排序

在上面的過程中,我們將不是當(dāng)前對(duì)象的整數(shù)倍的存儲(chǔ)位置pass掉了,這是為什么?我們通過畫圖來理解


02-內(nèi)存對(duì)齊原因.png

當(dāng)我們看到第一種取法,發(fā)現(xiàn)每次變化取值長(zhǎng)度,一共需要3次才取完,而第二種取法直接按照最大長(zhǎng)度去取,不夠的位置空出來,后續(xù)如果有滿足條件的在優(yōu)化的過程中插進(jìn)去(1+4+3->1+3+4)這樣我們只需要兩次就全部取完了,這與我們第一篇文章講的字節(jié)對(duì)齊,以空間換時(shí)間,是異曲同工之妙。

下面我們?cè)僮鲆粋€(gè)練習(xí)鞏固下

struct LGStruct3 {
    double a;     // 8字節(jié)    [0 7]
    int b;        // 4字節(jié)    [8 9 10 11]
    char c;       // 1字節(jié)    [12]
    short d;      // 2字節(jié)    (13 [14 15]   
    int e;        // 4字節(jié)    [16 17 18 19]   24
    struct HZMStruct1 str;    (20 21 22 23 [24~ 41]  ->48
}struct3;

malloc源碼引入

02-LGPerson.png
02-malloc.png

第一個(gè):<LGPerson: 0x100542960>應(yīng)該不用過多解釋了
第二個(gè):我們可以理解為person是個(gè)對(duì)象,對(duì)象的本質(zhì)就是指針地址,指針大小為8字節(jié)
第三個(gè):LGPerson的各個(gè)成員變量相加,8+8+4+8=28 ->32 但是要注意還有一個(gè)isa,所以32+8=40
第四個(gè):40->48是因?yàn)樯赌??我們看?code>malloc_size我們只能通過源碼進(jìn)行分析
接下來就就還是進(jìn)入我們的源碼文件進(jìn)行分析

malloc分析探索思路

首先從alloc進(jìn)入objc的源碼,找到obj = (id)calloc(1, size);
操作,涉及的方法順序是alloc --> _objc_rootAlloc --> callAlloc --> _objc_rootAllocWithZone --> _class_createInstanceFromZone

02-alloc源碼探索.png

這里calloc的探索需要切換到 libmalloc源碼中,可以在opensource下載最新版,接著往下走

1、在可編譯的libmalloc中定義一個(gè)可編譯的target,在main中使用calloc創(chuàng)建一個(gè)指針

02-libmalloc源碼.png

2、進(jìn)入_malloc_zone_calloc的源碼實(shí)現(xiàn),關(guān)鍵代碼是1560行zone->calloc(zone, num_items, size);

02-_malloc_zone_calloc.png

3、進(jìn)入zone->alloc的源碼,源碼就無法繼續(xù)跟進(jìn)了


02-zone->alloc.png

重點(diǎn):為了繼續(xù)深入了解,我們?cè)?code>ptr = zone->calloc(zone, num_items, size);處,加一個(gè)斷點(diǎn),然后運(yùn)行。
斷住后,通過打印得知zone->calloc的源碼實(shí)現(xiàn)在default_zone_calloc方法,然后全局搜索default_zone_calloc方法,找到具體實(shí)現(xiàn)

02-p zone->calloc.png

4、進(jìn)入calloc的源碼實(shí)現(xiàn),其中主要由兩部分操作

  • 創(chuàng)建真正的zone,即runtime_default_zone方法
  • 使用真正的zone進(jìn)行calloc
    02-default_zone_calloc.png

5、斷點(diǎn)走到return后,繼續(xù)打印


02-po zone->calloc.png

6、搜索nano_calloc進(jìn)入,其中的關(guān)鍵代碼是888行的返回值,此時(shí)的p是pointer表示指針 和前面的 ptr一樣

02-nano_calloc關(guān)鍵代碼.png

7、進(jìn)入_nano_malloc_check_clear源碼,將if else 折疊,看主流程

02-_nano_malloc_check_clear.png

其中segregated_next_block就是指針內(nèi)存開辟算法,目的是找到合適的內(nèi)存并返回
slot_bytes是加密算法的(其目的是為了讓加密算法更加安全,本質(zhì)就是一串自定義的數(shù)字)

8、進(jìn)入segregated_next_block方法,這個(gè)方法主要就是獲取內(nèi)存指針

02-segregated_next_block.png

整個(gè)流程大概意思就是不斷循環(huán)查找能夠容納需要的大小的空間,如果找到直接返回空間地址,如果找不到返回0。

9、進(jìn)入segregated_size_to_fit加密算法源碼, 通過算法邏輯,可以看出,其本質(zhì)就會(huì)16字節(jié)對(duì)齊算法

02-segregated_size_to_fit.png

所以在我們的堆里面,整個(gè)對(duì)象的內(nèi)存是以16字節(jié)對(duì)齊,成員變量是以8字節(jié)對(duì)齊(結(jié)構(gòu)體內(nèi)部),對(duì)象與對(duì)象之間因?yàn)槭窃谡麄€(gè)內(nèi)存中,所以也是16字節(jié)對(duì)齊

02-zone->alloc.png

所以我們之前的輸出結(jié)果為 <LGPerson: 0x100542960> - 8 - 40 - 48 最后一項(xiàng)是48

最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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