iOS底層原理探究 - alloc的底層原理

在iOS開發(fā)的過程中昧辽,我們最熟悉的就是對象瘪校,經(jīng)常會(huì)使用到的一個(gè)函數(shù):alloc,那這個(gè)函數(shù)的底層到底做了什么呢 九孩?我們一起一探究竟先馆。

開始探索前,先看一下探索過程中可能用到的一些指令躺彬!

一煤墙、常用指令

1. po:   為 print object 的縮寫,顯示對象的文本描述
2. bt:   打印函數(shù)的堆棧  
3. register read    讀取寄存器
4. x/nuf 
    n表示要顯示的內(nèi)存單元的個(gè)數(shù)
    u表示一個(gè)地址單元的長度:
    取值范圍: 
            b 單字節(jié)  
            h 表示雙字節(jié)
            w 表示四字節(jié)
            g 表示八字節(jié)
    f表示顯示方式:
    取值范圍:
            x 按十六進(jìn)制格式
            d 按十進(jìn)制格式
            u 按十進(jìn)制格式顯示無符號
            o 按八進(jìn)制格式
            t 按二進(jìn)制格式
            a 按十六進(jìn)制格式
            i 指令地址格式  
            c 按字符格式
            f 按浮點(diǎn)數(shù)格式

持續(xù)更新中...

二宪拥、alloc做了什么仿野?

通過以下代碼我們可以知道alloc是向系統(tǒng)申請內(nèi)存空間

    JLPerson *p1 = [JLPerson alloc];
    JLPerson *p2 = [p1 init];
    JLPerson *p3 = [p1 init];
    
    NSLog(@"%@-%p-%p",p1,p1,&p1);
    NSLog(@"%@-%p-%p",p2,p2,&p2);
    NSLog(@"%@-%p-%p",p3,p3,&p3);
-----------------------------------------------------------
  <JLPerson: 0x6000032c8020>-0x6000032c8020-0x7ffeef26e1a8
  <JLPerson: 0x6000032c8020>-0x6000032c8020-0x7ffeef26e1a0
  <JLPerson: 0x6000032c8020>-0x6000032c8020-0x7ffeef26e198

從上面的代碼中可以看出p1、p2她君、p3的指針地址之間是相差8個(gè)字節(jié)脚作,并且地址是連續(xù)的,這就符合棧內(nèi)存的分配原則

根據(jù)代碼的演示我們可以得到以下的圖示球涛。


圖片1.png

總結(jié):指針地址是在棧內(nèi)存劣针,申請的內(nèi)存空間在堆內(nèi)存

三亿扁、alloc底層是怎么調(diào)用捺典?

我們已經(jīng)知道了alloc是申請內(nèi)存空間,那么它是怎么申請內(nèi)存的呢从祝?申請多少內(nèi)存空間襟己?內(nèi)存的大小怎么計(jì)算?
帶著這些問題往下探索牍陌。

三種探索底層的方式:

  • 下符號斷點(diǎn)的形式直接跟流程: Symbolic Breakpoint
  • 按住control -> step into
  • 匯編查看跟流程:Debug -> Debug workflow -> Always show Disassembly

通過上面三種方式我們知道了alloc底層是屬于libobjc庫稀蟋,我們將源碼下載編譯跑起來。

蘋果開源源碼匯總: https://opensource.apple.com
這個(gè)地址?的更直接: https://opensource.apple.com/tarballs/

  • 發(fā)現(xiàn)問題

首先我們對下載的源碼進(jìn)行編譯呐赡,對alloc函數(shù)進(jìn)行斷點(diǎn)跟蹤(也可以使用符號斷點(diǎn)或者匯編的方式進(jìn)行),按照正常的思維流程應(yīng)該是響應(yīng)alloc函數(shù)的底層調(diào)用骏融,但是真正的調(diào)試卻是走了objc_alloc链嘀,這是為什么呢?

  • 探索問題
  1. 通過對源碼進(jìn)行全局搜索 objc_alloc档玻,對結(jié)果一個(gè)個(gè)解讀我們可以從中發(fā)現(xiàn)一個(gè)函數(shù)fixupMessageRef怀泊,里面有一個(gè)if (msg->sel == @selector(alloc))判斷,滿足條件就是msg指向的imp 替換成objc_alloc误趴。

    objc_alloc.png

  2. 既然找到了fixupMessageRef霹琼,那么我順著這條思路找一找fixupMessageRef是什么時(shí)候調(diào)用的呢?
    通過逆向的查找我們可以得出以下的一個(gè)調(diào)用流程:
    fixupMessageRef<--_read_images<--map_images_nolock<--map_images<--_dyld_objc_notify_register<--_objc_init
    把這些函數(shù)全部打上斷點(diǎn)凉当,運(yùn)行程序枣申,看是否如我們所想的那樣進(jìn)行了IMP的替換;運(yùn)行后發(fā)現(xiàn)還是會(huì)走objc_alloc方法看杭,但是并沒有走fixupMessageRef方法進(jìn)行替換忠藤。為什么會(huì)提供一個(gè)不被執(zhí)行的修復(fù)函數(shù)呢?難道是因?yàn)樵诰幾g的過程中就有可能發(fā)生問題楼雹,然后做一個(gè)容錯(cuò)的處理嗎模孩?

  3. 找到LLVM的源碼,通過解讀LLVM的源碼可以得出alloc贮缅、release榨咐、autoRelease等一些方法在編譯的過程中LLVM會(huì)對這些函數(shù)進(jìn)行Hook攔截

我們已經(jīng)知道了為什么要走objc_alloc方法了,那對于alloc主線的流程通過斷點(diǎn)方式跟下來就可以了谴供。

  • alloc調(diào)用流程圖:
alloc調(diào)用流程圖.png
  1. alloc的主線流程圖我們已經(jīng)比較清晰了块茁,接下來我們重點(diǎn)看一下 _class_createInstanceFromZone這個(gè)函數(shù)的實(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)
{
    ...
    size_t size;
    # 計(jì)算當(dāng)前類需要開辟的內(nèi)存空間大小
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;
    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        # 申請內(nèi)存空間
        obj = (id)calloc(1, size);
    }
    ...
    if (!zone && fast) {
        # 將類cls和obj指針進(jìn)行關(guān)聯(lián)
        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);
}
  1. 我們重點(diǎn)看一下instanceSize這個(gè)函數(shù)憔鬼,主要用來計(jì)算當(dāng)前類需要開辟的內(nèi)存空間大小龟劲。
看一下函數(shù)的整個(gè)流程圖:
instanceSize流程圖.png
字節(jié)對齊算法:
字節(jié)對齊算法.png

問題1:為什么alloc第一次會(huì)進(jìn)objc_alloc胃夏,然后才會(huì)進(jìn)去_objc_rootAlloc ?
(LLVM底層對objc_alloc進(jìn)行攔截)

擴(kuò)展:
  • init 初始化,使用工廠模式昌跌,可以對其進(jìn)行重寫仰禀,用來擴(kuò)展
  • new 底層是allocinit的組合,直接使用new相對于使用alloc init擴(kuò)展性更差了

四蚕愤、內(nèi)存對齊原則

前言:

1.屬性和成員變量會(huì)影響內(nèi)存大小答恶,方法不影響內(nèi)存大小
2.oc對象開辟內(nèi)存空間大小是以16字節(jié)對齊,對象的成員變量的字節(jié)是以8字節(jié)對齊

各類型字節(jié)大衅加铡:
字節(jié)大小.png
問題1:為什么需要字節(jié)對齊悬嗓?
  • 通常內(nèi)存是由字節(jié)組成,cpu在存取數(shù)據(jù)時(shí)裕坊,是以為單位存取包竹,的大小決定了內(nèi)存存取的力度。頻繁的存取未對齊的數(shù)據(jù)籍凝,會(huì)降低cpu的性能周瞎,所以可以通過內(nèi)存對齊的方式來減少存取次數(shù),從而達(dá)到降低cpu的開銷,以空間來換取時(shí)間饵蒂。
問題2:為什么oc對象開辟內(nèi)存空間是以16字節(jié)對齊声诸?
  • 由于在一個(gè)對象中,第一個(gè)屬性isa8字節(jié)退盯,一個(gè)對象中肯定還會(huì)包含其他的屬性成員變量彼乌,系統(tǒng)會(huì)預(yù)留8字節(jié),即16字節(jié)對齊渊迁,而如果是8字節(jié)對齊的話慰照,該對象的isa和下一個(gè)對象的isa緊挨著,訪問時(shí)容易造成訪問混亂琉朽。
  • 16字節(jié)對齊焚挠,可以加快cpu讀取速度,也可以使訪問更加安全漓骚。
下面我們看一下結(jié)構(gòu)體的內(nèi)存對齊
struct LGStruct1 {
    double a;       // 8    [0 7]
    char b;         // 1    [8]
    int c;          // 4    [12 13 14 15]  (9 10 11空3個(gè)字節(jié) 12是4的倍數(shù)) 
    short d;        // 2    [16 17]
}struct1;
# 根據(jù)字節(jié)對齊是8字節(jié)原則  8的倍數(shù)最后為   24

struct LGStruct2 {
    double a;       // 8    [0 7]
    int b;          // 4    [8 9 10 11]    (8是4的倍數(shù))
    char c;         // 1    [12]
    short d;        // 2    [14 15]    (13 空1個(gè)字節(jié)  14是2的倍數(shù))
}struct2;
# 根據(jù)字節(jié)對齊是8字節(jié)原則  8的倍數(shù)最后為   16

從上述代碼中可以看出蝌衔,結(jié)構(gòu)體的屬性都一樣,屬性的順序不一樣蝌蹂,內(nèi)存大小也不一樣噩斟。

上面是單個(gè)結(jié)構(gòu)體的內(nèi)存對齊,如果結(jié)構(gòu)體嵌套又是怎樣的呢孤个?
struct LGStruct1 {
    double a;       // 8   
    char b;         // 1    
    int   c;        //4
    short d;        // 2   
}struct1;

struct LGStruct3 {
    double a;    //8     [0 --> 7]
    int b;       //4    [8  9  10  11 ]
    char c;      //1   [12]
    short d;     //2   [14  15]
    int e;       //4    [16  17  18  19]
    struct LGStruct1 {
        double a;       // 8    [24 --> 31]
        char b;         // 1    [32]
        int   c;        //4   [36  37  38   39]
        short d;        // 2    [40  41]
    }str;
}struct3;
# 將struct3進(jìn)行展開, 根據(jù)8字節(jié)內(nèi)存對齊原則剃允,最終輸出為 48
總結(jié):
一般結(jié)構(gòu)體大小
  • 1.結(jié)構(gòu)體成員的偏移量必須是成員大小的整數(shù)倍
  • 2.結(jié)構(gòu)體大小是最大元素的倍數(shù)(最大元素字節(jié)對齊)
嵌套結(jié)構(gòu)體大小
  • 1.展開后的結(jié)構(gòu)體的第一個(gè)成員的偏移量應(yīng)當(dāng)是被展開的結(jié)構(gòu)體中最大的成員變量的整數(shù)倍
  • 2.結(jié)構(gòu)體大小必須是所有成員中最大元素的整數(shù)倍(8字節(jié)對齊)

如果以上內(nèi)容有錯(cuò)誤的地方,還請各位大佬指點(diǎn)!
持續(xù)更新和修復(fù)中...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末斥废,一起剝皮案震驚了整個(gè)濱河市椒楣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌牡肉,老刑警劉巖捧灰,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異统锤,居然都是意外死亡毛俏,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門饲窿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來煌寇,“玉大人,你說我怎么就攤上這事逾雄》埽” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵鸦泳,是天一觀的道長淌哟。 經(jīng)常有香客問我,道長辽故,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任腐碱,我火速辦了婚禮誊垢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘症见。我一直安慰自己喂走,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布谋作。 她就那樣靜靜地躺著芋肠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪遵蚜。 梳的紋絲不亂的頭發(fā)上帖池,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機(jī)與錄音吭净,去河邊找鬼睡汹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛寂殉,可吹牛的內(nèi)容都是我干的囚巴。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼彤叉!你這毒婦竟也來了庶柿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤秽浇,失蹤者是張志新(化名)和其女友劉穎浮庐,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兼呵,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡兔辅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了击喂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片维苔。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖懂昂,靈堂內(nèi)的尸體忽然破棺而出介时,到底是詐尸還是另有隱情,我是刑警寧澤凌彬,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布沸柔,位于F島的核電站,受9級特大地震影響铲敛,放射性物質(zhì)發(fā)生泄漏褐澎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一伐蒋、第九天 我趴在偏房一處隱蔽的房頂上張望工三。 院中可真熱鬧,春花似錦先鱼、人聲如沸俭正。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掸读。三九已至,卻和暖如春宏多,著一層夾襖步出監(jiān)牢的瞬間儿惫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工伸但, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留姥闪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓砌烁,卻偏偏與公主長得像筐喳,于是被迫代替她去往敵國和親催式。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容