OC對象底層探索 — 由字節(jié)對齊到對象內(nèi)存的分配

用于記錄iOS底層學(xué)習(xí),以備后續(xù)回顧

OC對象底層探索

前言

我們繼續(xù)來探索對象是如何申請熟尉、開辟慷荔、優(yōu)化內(nèi)存大小的耗跛。
要想了解對象的內(nèi)存優(yōu)化首先要知道內(nèi)存對齊原則(理論加實(shí)踐一點(diǎn)點(diǎn)的搞懂)菊碟。

一节芥、內(nèi)存對齊

1.1 內(nèi)存對齊的三個(gè)規(guī)則

  • a. 數(shù)據(jù)成員對齊規(guī)則:結(jié)構(gòu)體(struct)(或聯(lián)合體(union))的數(shù)據(jù)成員,第一個(gè)數(shù)據(jù)成員放在offset為0的地方逆害,以后每個(gè)數(shù)據(jù)成員存儲的起始位置要從該成員大小或者成員的子成員大小(只要該成員有子成員头镊,比如數(shù)組、結(jié)構(gòu)體等)的整數(shù)倍開始(比如int為4字節(jié)魄幕,則要從4的整數(shù)倍地址開始存儲)拧晕。
  • b. 結(jié)構(gòu)體作為成員:如果一個(gè)結(jié)構(gòu)里有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從其內(nèi)部最大元素大小的整數(shù)倍地址開始存儲(struct a里存有struct b梅垄,b里有char,int输玷,double等元素队丝,那b應(yīng)該從8的整數(shù)倍開始存儲)
  • c. 最后判斷:結(jié)構(gòu)體的總大小,也就是sizeof的結(jié)果欲鹏,必須是其內(nèi)部最大成員的整數(shù)倍机久,不足要補(bǔ)齊

1.2 內(nèi)存對齊代碼示例探究

經(jīng)過實(shí)際探究,下列示例已涵蓋內(nèi)存對齊的全部原則并加以備注

struct DZStruct1 {
    char a;     // 1    [0]
    double b;   // 8    [8, 15]
    int c;      // 4    [16, 19]
    short d;    // 2    [20, 22]
} MyStruct1; // sizeof(MyStruct1) = 24字節(jié)

struct DZStruct2 {
    double b;   // 8    [0, 7]
    char a;     // 1    [8]
    int c;      // 4    [12, 15] 按順序從9開始赔嚎,但是起始位置需要從該成員大小的整數(shù)倍開始膘盖,所以起始位置9需要往大了走胧弛,到12為4的整數(shù)倍
    short d;    // 2    [16, 17]
} MyStruct2; // sizeof(MyStruct2) = 24字節(jié),結(jié)構(gòu)體的總大小侠畔,必須是其內(nèi)部最大成員的整數(shù)倍结缚,不足要補(bǔ)齊所以要從17補(bǔ)齊到24為8的整數(shù)倍

struct DZStruct3 {
    char a;     // 1    [0]
    struct DZStruct1 struct1;   // 24   [8, 31] 按順序從1開始,但是起始位置需要從該結(jié)構(gòu)體(DZStruct1)內(nèi)部最大元素大小的整數(shù)倍開始存儲软棺,所以從1往大了走红竭,到8為8的整數(shù)倍
    double b;   // 8    [32, 39]
    short d;    // 2    [40, 41]
} MyStruct3; // sizeof(MyStruct3) = 48字節(jié)

1.3 字節(jié)基礎(chǔ)補(bǔ)充

a. 先簡單補(bǔ)充一點(diǎn)字節(jié)相關(guān)基礎(chǔ)知識:
sizeof()是運(yùn)算符,編譯的時(shí)候就是一個(gè)確定的數(shù)據(jù)會替換為常數(shù)喘落,返回的是一個(gè)類型所占內(nèi)存的字節(jié)大小茵宪。

b. 了解獲取內(nèi)存的三個(gè)方法

  • sizeof是運(yùn)算符,編譯的時(shí)候就替換為常數(shù)瘦棋,返回的是一個(gè)類型所占內(nèi)存的大小
  • class_getInstanceSize傳入一個(gè)類對象稀火,返回一個(gè)對象的實(shí)例至少需要多少內(nèi)存,它等價(jià)于sizeof,需要導(dǎo)入#import <objc/runtime.h>
  • malloc_size返回系統(tǒng)實(shí)際分配的內(nèi)存大小赌朋,需要導(dǎo)入#import <malloc/malloc.h>

二凰狞、探索對象申請內(nèi)存和系統(tǒng)分配內(nèi)存

2.1 首先對無成員變量對象進(jìn)行探索

DZTeacher.h文件

#import <Foundation/Foundation.h>
@interface DZTeacher : NSObject

main.m文件

#import <Foundation/Foundation.h>
#import "DZTeacher.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        DZTeacher  *p = [DZTeacher alloc];
        NSLog(@"申請內(nèi)存大小為:%lu——-系統(tǒng)開辟內(nèi)存大小為:%lu",class_getInstanceSize([p class]),malloc_size((__bridge const void *)(p)));
    }
    return 0;
}
分析結(jié)果:無成員變量對象應(yīng)該分配內(nèi)存:isa的8字節(jié)(isa后續(xù)會進(jìn)行詳細(xì)探索)
實(shí)際打印結(jié)果 : 
申請內(nèi)存大小為:8——-系統(tǒng)開辟內(nèi)存大小為:16

class_getInstanceSize 內(nèi)部實(shí)現(xiàn)如下:

size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize(); // 直接返回字節(jié)對齊大小
}

回憶上一篇文章, alloc 初始化往下探索其中有一個(gè)方法_class_createInstanceFromZone箕慧,內(nèi)部會調(diào)用size_t size = cls->instanceSize(extraBytes)服球,具體實(shí)現(xiàn)如下:

size_t instanceSize(size_t extraBytes) {
    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;  // 最小返回16
    return size;
}

我們通過查看OC源碼可知,class_getInstanceSize返回類的成員變量占據(jù)內(nèi)存的大小颠焦,而malloc_size獲取obj指針指向內(nèi)存的大小斩熊。
分析得出:系統(tǒng)在創(chuàng)建一個(gè)對象的時(shí)候,對象的isa指針占據(jù)8個(gè)字節(jié)伐庭,但是系統(tǒng)會為其分配最少16字節(jié)的內(nèi)存空間粉渠,所以如果該對象沒有成員變量, class_getInstanceSize 會輸出 8 個(gè)字節(jié)圾另,malloc_size 會輸出最少 16 個(gè)字節(jié)霸株。

2.2 添加成員變量后對象內(nèi)存探索

DZTeacher.h文件

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN
@interface DZTeacher : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;
@property (nonatomic, strong) NSString *hobby;

main.m文件

#import <Foundation/Foundation.h>
#import "DZTeacher.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        DZTeacher  *p = [DZTeacher alloc];
        // isa ---- 8
        p.name = @"Dezi";   // sizeof(p.name) = 8
        p.age  = 18;            // 4
        p.height = 185;         // 8
        p.hobby  = @"女";       // 8
        NSLog(@"%@",p);
        NSLog(@"申請內(nèi)存大小為:%lu——-系統(tǒng)開辟內(nèi)存大小為:%lu",class_getInstanceSize([p class]),malloc_size((__bridge const void *)(p)));
    }
    return 0;
}
分析結(jié)果:
1. 成員變量應(yīng)該分配內(nèi)存:8字節(jié) + 4字節(jié) + 8字節(jié) + 8字節(jié) = 28字節(jié)
2. 再加上isa的8字節(jié) = 36字節(jié)(isa后續(xù)會進(jìn)行詳細(xì)探索)
3. 根據(jù)字節(jié)對齊原則最大8字節(jié),所以36往大走補(bǔ)充到40字節(jié)
4. 所以內(nèi)存空間應(yīng)該分配40字節(jié)
5. 注意:對象開辟空間的時(shí)候成員變量就會編譯進(jìn)來集乔,所以成員變量未賦值也會分配內(nèi)存

實(shí)際打印結(jié)果 : 
申請內(nèi)存大小為:40——-系統(tǒng)開辟內(nèi)存大小為:48

根據(jù)上方打印信息去件,我們的分析是對的,類對象至少需要40字節(jié)扰路,那為什么實(shí)際分配內(nèi)存大小為48字節(jié)呢尤溜?下來我們繼續(xù)探索。

2.2 calloc探索

經(jīng)過對源碼的一步步探索汗唱,我們發(fā)現(xiàn)宫莱,在obj = (id)calloc(1, size);這個(gè)方法的時(shí)候,對象內(nèi)存大小發(fā)生了變化

calloc方法探索

malloc.png

根據(jù)上圖我們發(fā)現(xiàn)calloc方法在malloc源碼里邊哩罪,那我們打開新的源碼繼續(xù)分析:

DZCallocTest.png

之后進(jìn)入calloc流程授霸,首先調(diào)用malloc_zone_calloc方法

calloc.png

在其內(nèi)部調(diào)用zone->calloc初始化并且返回了一個(gè)ptr指針

malloc_zone_calloc.png

斷點(diǎn)在此處我們發(fā)現(xiàn)遞歸了巡验,那我們在這里打印zone->calloc,我們找到malloc.c文件249行的default_zone_calloc方法

zone->calloc.png

加上斷點(diǎn)我們發(fā)現(xiàn)此處又是zone->calloc碘耳,繼續(xù)打印zone->calloc發(fā)現(xiàn)nano_malloc.c文件中878行的nano_calloc方法

default_zone_calloc.png

在malloc的源碼中搜索nano_calloc显设,于nano_calloc.c文件中找到該方法,其中的核心代碼_nano_malloc_check_clear進(jìn)行內(nèi)存申請藏畅,并且返回一個(gè)指針p

zone->calloc.png

_nano_malloc_check_clear內(nèi)部發(fā)現(xiàn)segregated_size_to_fit方法輸入的size是40敷硅,輸出的是48,所以這個(gè)方法是開辟內(nèi)存的算法

_nano_malloc_check_clear.png

segregated_size_to_fit方法進(jìn)行分析愉阎,發(fā)現(xiàn)對齊原則是16字節(jié)對齊绞蹦,所以輸入實(shí)際需要的40,經(jīng)過16字節(jié)對齊后輸出的為48

segregated_size_to_fit.png
nano_zone_common.png

2.3 總結(jié)+流程圖

對象內(nèi)存大小的申請是按照8字節(jié)對齊榜旦,不滿16字節(jié)時(shí)按照16字節(jié)計(jì)算幽七;若大于16字節(jié)時(shí),calloc實(shí)際開辟內(nèi)存則是按照16字節(jié)對齊溅呢。
感覺8字節(jié)對齊是為了屬性之間內(nèi)存安全提高容錯(cuò)空間澡屡,16字節(jié)對齊是為了保證對象之間內(nèi)存安全提高容錯(cuò)空間。

calloc.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咐旧,一起剝皮案震驚了整個(gè)濱河市驶鹉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌铣墨,老刑警劉巖室埋,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異伊约,居然都是意外死亡姚淆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門屡律,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腌逢,“玉大人,你說我怎么就攤上這事超埋〔龋” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵霍殴,是天一觀的道長窍蓝。 經(jīng)常有香客問我,道長繁成,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任淑玫,我火速辦了婚禮巾腕,結(jié)果婚禮上面睛,老公的妹妹穿的比我還像新娘。我一直安慰自己尊搬,他們只是感情好叁鉴,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著佛寿,像睡著了一般幌墓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上冀泻,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天常侣,我揣著相機(jī)與錄音,去河邊找鬼弹渔。 笑死胳施,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的肢专。 我是一名探鬼主播舞肆,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼博杖!你這毒婦竟也來了椿胯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤剃根,失蹤者是張志新(化名)和其女友劉穎哩盲,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體跟继,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡种冬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了舔糖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片娱两。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖金吗,靈堂內(nèi)的尸體忽然破棺而出十兢,到底是詐尸還是另有隱情,我是刑警寧澤摇庙,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布旱物,位于F島的核電站,受9級特大地震影響卫袒,放射性物質(zhì)發(fā)生泄漏宵呛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一夕凝、第九天 我趴在偏房一處隱蔽的房頂上張望宝穗。 院中可真熱鬧户秤,春花似錦、人聲如沸逮矛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽须鼎。三九已至鲸伴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間晋控,已是汗流浹背汞窗。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留糖荒,地道東北人杉辙。 一個(gè)月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像捶朵,于是被迫代替她去往敵國和親蜘矢。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

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

  • 前話: 在了解內(nèi)存對齊之前先了解一下各數(shù)據(jù)類型在內(nèi)存中的大小综看,目前我們比較常用的是64位系統(tǒng)品腹,所以我們的研究對象統(tǒng)...
    sz_藍(lán)天使者閱讀 706評論 0 3
  • 前言 iOS底層探索之對象原理(一)中了解到通過calloc我們對象有了內(nèi)存地址,通過initInstanceIs...
    litongde閱讀 220評論 0 2
  • iOS底層探索 - 內(nèi)存補(bǔ)齊 在上篇文章中我們主要探索了對象的初始化以及怎么開辟內(nèi)存红碑。內(nèi)存對齊三大原則是什么舞吭?對象...
    leng_li閱讀 267評論 0 0
  • 作者:李振君 這個(gè)孤僻的泥瓦匠 只會把沉默砌成墻 什么也不說; 他唯一的愛好就是 收工后 用滿腹的心事 讓一個(gè)酒瓶...
    李振君閱讀 231評論 0 4
  • 今天爸爸和媽媽都上班析珊,只有我和姐姐在家里羡鸥。 早上我們醒來的時(shí)候。我和姐姐就泡了方便面忠寻,吃完了之...
    王啟萱閱讀 314評論 0 0