第二節(jié)課 OC對象原理(中)
底層LLVM優(yōu)化
上篇文章我們說到,實際代碼查看的流程是 alloc->_objc_rootAlloc->callAlloc->_objc_rootAllocWithZone
,但是,我們通過斷點發(fā)現(xiàn)實際流程卻是alloc->objc_alloc->callAlloc->objc_msgSend->alloc->_objc_rootAlloc-callAlloc
,這個到底是為啥呢劣领?
原因是蘋果覺得alloc
是比較特殊的方法瞪浸,只要是alloc
购岗,就先走objc_alloc
(類似hook),執(zhí)行了一些底層優(yōu)化氨距、標記精偿,再執(zhí)行alloc
方法针贬,具體需要探索LLVM的源碼钓觉,這個我們后續(xù)再進行補充。
LLVM下載地址
所以實際的alloc流程應該為:
alloc->objc_alloc->LLVM底層優(yōu)化坚踩、標記等等->objc_alloc->objc_msgSend->alloc->LLVM判斷標記過->_objc_rootAlloc
這也是為什么callalloc走兩次的原因
荡灾,所以我們將之前的流程圖再次補充一下。
對象的內(nèi)存的影響因素
上篇文章我們討論了一下字節(jié)對齊瞬铸,字節(jié)對齊的最終又是以內(nèi)存的方式展現(xiàn)批幌,所以我們來探究一下能影響內(nèi)存的因素。
先看下我們上篇文章寫的例子
正常的class_getInstanceSize,是32嗓节,那我們干掉一些屬性后呢荧缘?發(fā)現(xiàn)有減少
這證明我們的屬性是對內(nèi)存有影響的,那成員變量應該也是一樣的拦宣,我們添加后發(fā)現(xiàn)截粗,確實影響了內(nèi)存大小。
那么方法呢鸵隧?添加了一個方法后發(fā)現(xiàn)并沒有變化绸罗,因為方法不占用內(nèi)存,這個我們后續(xù)會再詳細進行講解豆瘫。對于內(nèi)存的理解我們可以先看下面的圖珊蟀。
Person
通過alloc
開辟了一塊堆的空間,外部通過對象的地址(棧里)來進行指向外驱。這個內(nèi)存空間里的就是Person里面的各項成員變量
以及isa
育灸。
我們新增幾條屬性后,通過x/8gx
輸出看到昵宇,左邊0x60000336b6c0
為首地址磅崭,對應的第一個對象是0x0000000101968888
,也就是isa
瓦哎。后面按順序排列的依次是各個屬性變量砸喻,每8字節(jié)一個對象柔逼,這也就是我們對齊原則。
需要注意的一點是我們的190.5是po不出來恩够,我們可以使用e -f f-- 0x4067d00000000000
,或者p/f 0x4067d00000000000
.因為我們正常的po打印不出來,double與float類型需要單獨輸出打印
結(jié)構(gòu)體內(nèi)存對齊
剛才的例子中羡铲,我們發(fā)現(xiàn)第二個變量蜂桶,0x0000001200006261
實際上是3個變量組成的,0x12也切、0x62扑媚、0x61
,這個是由于進行了響應的內(nèi)存對齊雷恃,那我們就來看看結(jié)構(gòu)體的內(nèi)存對齊原則
1:數(shù)據(jù)成員對齊規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員疆股,第一個數(shù)據(jù)成員放在offset為0的地方
,以后每個數(shù)據(jù)成員存儲的起始位置要從該成員大小或者成員的子成員大小
(只要該成員有子成員倒槐,比如說是數(shù)組旬痹,結(jié)構(gòu)體等)的整數(shù)倍開始
(比如int在32位機為4字節(jié),則要從4的整數(shù)倍地址開始存儲。
2:結(jié)構(gòu)體作為成員:如果一個結(jié)構(gòu)里有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從其內(nèi)部最大元素大小的整數(shù)倍地址開始存儲
.(struct a里存有struct b,b里有char,int ,double等元素,那b應該從8的整數(shù)倍開始存儲.)
3:收尾工作:結(jié)構(gòu)體的總大小,也就是sizeof的結(jié)果
,.必須是其內(nèi)部最大成員的整數(shù)倍
.不足的要補齊讨越。
我們先來看兩個結(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;
這兩個結(jié)構(gòu)體的成員變量完全一樣两残,只是順序不一樣,這樣的兩個結(jié)構(gòu)體的sizeof會一樣嘛把跨?直接上結(jié)果
Why人弓?
double a; // 8字節(jié) 存儲位置[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;
在我們存儲的過程中,其實蘋果會自動幫我們進行最優(yōu)化排序
在上面的過程中着逐,我們將不是當前對象的整數(shù)倍的存儲位置pass掉了崔赌,這是為什么?我們通過畫圖來理解
當我們看到第一種取法耸别,發(fā)現(xiàn)每次變化取值長度健芭,一共需要3次才取完,而第二種取法直接按照最大長度去取秀姐,不夠的位置空出來吟榴,后續(xù)如果有滿足條件的在優(yōu)化的過程中插進去(1+4+3->1+3+4)這樣我們只需要兩次就全部取完了,這與我們第一篇文章講的字節(jié)對齊囊扳,以空間換時間吩翻,是異曲同工之妙。
下面我們再做一個練習鞏固下
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源碼引入
第一個:<LGPerson: 0x100542960>
應該不用過多解釋了
第二個:我們可以理解為person是個對象锥咸,對象的本質(zhì)就是指針地址狭瞎,指針大小為8字節(jié)
第三個:LGPerson的各個成員變量相加,8+8+4+8=28 ->32 但是要注意還有一個isa搏予,所以32+8=40
第四個:40->48是因為啥呢熊锭?我們看看malloc_size
我們只能通過源碼進行分析
接下來就就還是進入我們的源碼文件進行分析
malloc分析探索思路
首先從alloc進入objc的源碼,找到obj = (id)calloc(1, size);
操作,涉及的方法順序是alloc --> _objc_rootAlloc --> callAlloc --> _objc_rootAllocWithZone --> _class_createInstanceFromZone
這里calloc
的探索需要切換到 libmalloc源碼中
碗殷,可以在opensource下載最新版精绎,接著往下走
1、在可編譯的libmalloc
中定義一個可編譯的target
锌妻,在main
中使用calloc創(chuàng)建一個指針
2代乃、進入_malloc_zone_calloc
的源碼實現(xiàn),關鍵代碼是1560行
的zone->calloc(zone, num_items, size);
3仿粹、進入zone->alloc的源碼搁吓,源碼就無法繼續(xù)跟進了
重點:為了繼續(xù)深入了解,我們在ptr = zone->calloc(zone, num_items, size);
處吭历,加一個斷點堕仔,然后運行。
斷住后晌区,通過打印得知zone->calloc
的源碼實現(xiàn)在default_zone_calloc
方法摩骨,然后全局搜索default_zone_calloc
方法,找到具體實現(xiàn)
4朗若、進入calloc的源碼實現(xiàn)仿吞,其中主要由兩部分操作
- 創(chuàng)建真正的
zone
,即runtime_default_zone
方法 - 使用真正的
zone
進行calloc
5捡偏、斷點走到return后唤冈,繼續(xù)打印
6、搜索nano_calloc
進入银伟,其中的關鍵代碼是888行的返回值
你虹,此時的p是pointer表示指針 和前面的 ptr一樣
7、進入_nano_malloc_check_clear
源碼彤避,將if else 折疊傅物,看主流程
其中
segregated_next_block
就是指針內(nèi)存開辟算法,目的是找到合適的內(nèi)存并返回slot_bytes
是加密算法的鹽
(其目的是為了讓加密算法更加安全琉预,本質(zhì)就是一串自定義的數(shù)字)
8董饰、進入segregated_next_block
方法,這個方法主要就是獲取內(nèi)存指針
整個流程大概意思就是不斷循環(huán)查找能夠容納需要的大小的空間圆米,如果找到直接返回空間地址卒暂,如果找不到返回0。
9娄帖、進入segregated_size_to_fit
加密算法源碼, 通過算法邏輯也祠,可以看出,其本質(zhì)就會16字節(jié)對齊算法
所以在我們的堆里面近速,整個對象的內(nèi)存是以16字節(jié)對齊诈嘿,成員變量是以8字節(jié)對齊(結(jié)構(gòu)體內(nèi)部)堪旧,對象與對象之間因為是在整個內(nèi)存中,所以也是16字節(jié)對齊
所以我們之前的輸出結(jié)果為 <LGPerson: 0x100542960> - 8 - 40 - 48 最后一項是48