OC內(nèi)存大小的相關(guān)計(jì)算

原文鏈接OC內(nèi)存大小的相關(guān)計(jì)算

更新于2020-07-13

在面試的過程中挺物,我們較大概率地會(huì)被問一個(gè)類所占的內(nèi)存大小。本篇博客從下面一段測試代碼開始分析整個(gè)內(nèi)存大小的計(jì)算過程飘弧。

測試代碼如下:

struct A {
} TestA;

struct AA {
    char a;
} TestAA;

struct AAA {
    char a;
    int b;
} TestAAA;

@interface Person: NSObject {
    int _a;
}

@end

@implementation Person
@end

@interface Student1: Person {
    int _b;
}

@end

@implementation Student1
@end

@interface Student2: Person {
    int _b;
    int _c;
}

@end

@implementation Student2
@end

// 測試
+ (void)test {
    NSLog(@"TestA sizeof: %lu",sizeof(TestA));
    NSLog(@"TestAA sizeof: %lu",sizeof(TestAA));
    NSLog(@"TestAAA sizeof: %lu",sizeof(TestAAA));
    NSLog(@"--------------------------------------");
    NSLog(@"NSObject class_getInstanceSize = %zd", class_getInstanceSize([NSObject class]));
    NSLog(@"NSObject malloc_size = %zd", malloc_size((__bridge const void*)[NSObject new]));
    NSLog(@"--------------------------------------");
    NSLog(@"Person class_getInstanceSize = %zd", class_getInstanceSize([Person class]));
    NSLog(@"Person malloc_size = %zd", malloc_size((__bridge const void*)[Person new]));
    NSLog(@"--------------------------------------");
    NSLog(@"Student1 class_getInstanceSize = %zd", class_getInstanceSize([Student1 class]));
    NSLog(@"Student1 malloc_size = %zd", malloc_size((__bridge const void*)[Student1 new]));
    NSLog(@"Student1 sizeof = %zd", sizeof([Student1 class]));
    NSLog(@"--------------------------------------");
    NSLog(@"Student2 class_getInstanceSize = %zd", class_getInstanceSize([Student2 class]));
    NSLog(@"Student2 malloc_size = %zd", malloc_size((__bridge const void*)[Student2 new]));
}

執(zhí)行結(jié)果(運(yùn)行在模擬器下)如下:


class_getInstanceSize&malloc_size

其中class_getInstanceSize指的是成員變量占用的內(nèi)存大小识藤,malloc_size指的是指針指向內(nèi)存空間的大小即實(shí)際分配的內(nèi)存大小。

關(guān)于內(nèi)存大小的計(jì)算主要依賴于運(yùn)行環(huán)境以及內(nèi)存對(duì)齊次伶。上面的都是運(yùn)行在arm64環(huán)境下痴昧,因此內(nèi)存對(duì)齊決定了它們的值為什么不同。

內(nèi)存對(duì)齊

內(nèi)存對(duì)齊說白了就是為了提高CPU尋址操作性能的一種規(guī)則冠王。我們可以通過#pragma pack(n)赶撰,n=1、2柱彻、4豪娜、8、16 來改變這一系數(shù)绒疗,其中的n就是要指定的“對(duì)齊系數(shù)”侵歇。內(nèi)存對(duì)齊的規(guī)則如下:

  1. 數(shù)據(jù)成員對(duì)齊規(guī)則:結(jié)構(gòu)體或聯(lián)合體的第一個(gè)數(shù)據(jù)成員放在偏移為0的位置,以后每個(gè)數(shù)據(jù)成員的位置為min(對(duì)齊系數(shù)吓蘑,自身長度)的整數(shù)倍,下個(gè)位置不為本數(shù)據(jù)成員的整數(shù)倍位置的自動(dòng)補(bǔ)齊。
  2. 數(shù)據(jù)成員為結(jié)構(gòu)體:該數(shù)據(jù)成員的內(nèi)最大長度的整數(shù)倍的位置開始存儲(chǔ)磨镶。
  3. 整體對(duì)齊規(guī)則:數(shù)據(jù)成員按照1溃蔫,2步驟對(duì)齊之后,其自身也要對(duì)齊琳猫,對(duì)齊原則是min(對(duì)齊系數(shù)伟叛,數(shù)據(jù)成員最大長度)的整數(shù)倍。

內(nèi)存對(duì)齊計(jì)算

在64位編譯器環(huán)境下

代碼示例1:

// 對(duì)齊系數(shù)為8
#pragma pack(8)
struct AA {
    int a;   // 4字節(jié)
    char b;  // 1字節(jié)
    short c; // 2字節(jié)
    char d;  // 1字節(jié)
} Test1AA;
#pragma pack()

int main(int argc, char * argv[]) {
    @autoreleasepool { 
        NSLog(@"Test1AA: %lu",sizeof(Test1AA));
    }
}

執(zhí)行結(jié)果:

Test1AA: 12

計(jì)算過程如下:


內(nèi)存對(duì)齊計(jì)算1

代碼示例2:

#pragma pack(8)
struct AA {
    char a[2];
    short b;
    struct BB {
        int a;
        double b;
        float c;
    } Test2BB;
} Test2AA;
#pragma pack()

int main(int argc, char * argv[]) {
    
    @autoreleasepool {
        NSLog(@"Test2AA: %lu",sizeof(Test2AA));
    }
}

執(zhí)行結(jié)果:

Test2AA: 32

計(jì)算過程如下:


內(nèi)存對(duì)齊計(jì)算2

OC類的內(nèi)存分析

相關(guān)函數(shù)

class_getInstanceSize

上面講到class_getInstanceSize表示成員變量所占的內(nèi)存大小脐嫂,那么對(duì)于Person類創(chuàng)建的實(shí)例來說统刮,它的成員變量大小應(yīng)該為12,為什么結(jié)果卻是16账千。

class_getInstanceSize的內(nèi)存也有它自己的內(nèi)存對(duì)齊侥蒙,通過objc源碼中的class_getInstanceSize的底層實(shí)現(xiàn)可以知道,class_getInstanceSize的實(shí)現(xiàn)依賴于底層函數(shù)word_align匀奏,該函數(shù)返回的結(jié)果是8的倍數(shù)鞭衩,另外從alloc函數(shù)開始進(jìn)行分析,到instanceSize函數(shù)中可以知道所有對(duì)象的內(nèi)存大小至少是16個(gè)字節(jié)娃善,所以對(duì)象申請(qǐng)的內(nèi)存空間是以8字節(jié)進(jìn)行內(nèi)存對(duì)齊且至少是16個(gè)字節(jié)论衍。

word_align實(shí)現(xiàn)如下:

static inline uint32_t word_align(uint32_t x) {
    // WORD_MASK在64下的定義為7UL,就是7聚磺,所以相當(dāng)于(x + 7) & ~7
    // 0000 0111    -> 7
    // x:12坯台,12就是Person類中成員變量的大小
    // 12+7 = 19
    // 0001 0011    -> 19
    // &
    // 1111 1000    -> ~7  ~運(yùn)算,二進(jìn)制中瘫寝,0變1蜒蕾,1變0. 
    // 0001 0000    -> 16
    return (x + WORD_MASK) & ~WORD_MASK; 
}

malloc_size

通過malloc源碼中的segregated_size_to_fit函數(shù)可以知道系統(tǒng)開辟內(nèi)存空間是以16字節(jié)進(jìn)行內(nèi)存對(duì)齊。

static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey) {
    size_t k, slot_bytes;
    if (0 == size) {
        // NANO_REGIME_QUANTA_SIZE: (1 << SHIFT_NANO_QUANTUM) 即 16
        size = NANO_REGIME_QUANTA_SIZE;
    }
    
    // size: 8 
    // 0000 1000   -> 8
    // size + NANO_REGIME_QUANTA_SIZE - 1 = 8 + 15 = 23
    // 0001 0111   -> 23
    // >> 4
    // 0000 0001
    // << 4
    // 0001 0000   -> 16
    k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM;
    slot_bytes = k << SHIFT_NANO_QUANTUM;
    *pKey = k - 1;

    return slot_bytes;
}

Person矢沿、Student的內(nèi)存分析

回到一開始的測試代碼滥搭,根據(jù)上面內(nèi)存對(duì)齊的3個(gè)規(guī)則,Person捣鲸、Student1瑟匆、Student2的實(shí)際分配內(nèi)存(malloc_size)計(jì)算過程如下:


內(nèi)存對(duì)齊計(jì)算3

使用View Memory查看內(nèi)存

xcode_view_memory

從上面這張圖中我們可以知道,兩個(gè)16進(jìn)制代表1個(gè)字節(jié)栽惶,一行有32個(gè)字節(jié)愁溜。01 00 00 0003 00 00 00外厂、05 00 00 00(小端)對(duì)應(yīng)的就是_a冕象、_b_c汁蝶,另外前8個(gè)字節(jié)就是isa渐扮。整體內(nèi)存布局也與我們手動(dòng)計(jì)算實(shí)際內(nèi)存分配的結(jié)果一致论悴。

sizeof

sizeof用來返回類型的大小,其內(nèi)部也是進(jìn)行了內(nèi)存對(duì)齊的墓律。將測試代碼中的結(jié)構(gòu)體A膀估、AAAAA通過內(nèi)存對(duì)齊規(guī)則進(jìn)行分析耻讽,確實(shí)得到0察纯,18针肥。

這里我們使用結(jié)構(gòu)體AA進(jìn)行舉例:

  • 根據(jù)規(guī)則1饼记,數(shù)據(jù)成員占1個(gè)字節(jié),位于0號(hào)地址慰枕;
  • 無結(jié)構(gòu)體成員變量具则,跳過規(guī)則2;
  • 根據(jù)規(guī)則3捺僻,min(1, n) = 1乡洼,取1的整數(shù)倍,即結(jié)構(gòu)體分配的內(nèi)存大小為1個(gè)字節(jié)匕坯。

關(guān)于使用sizeof去獲取OC類內(nèi)存大小的時(shí)候束昵,我發(fā)現(xiàn)一個(gè)比較有意思的東西。

測試代碼如下:

struct N_NSObject_IMPL {
    Class isa;
};

struct N_Person_IMPL {
    struct N_NSObject_IMPL NSObject_IVARS;
    int _a;
};

struct N_Student_IMPL {
    struct N_Person_IMPL Person_IVARS;
    int _b;
    char _c;
};

@interface Person : NSObject {
    int _a;
}

@end

@implementation Person
@end

@interface Student : Person {
    int _b;
    char _c;
}

@end

@implementation Student
@end

// 測試
+ (void)test {
    NSObject *o = [NSObject new];
    struct N_NSObject_IMPL *so = (__bridge struct N_NSObject_IMPL *)o;
    NSLog(@"[NSObject new] sizeof = %lu", sizeof(o));
    NSLog(@"[NSObject class] sizeof = %lu", sizeof([NSObject class]));
    NSLog(@"struct N_NSObject_IMPL sizeof = %lu", sizeof(struct N_NSObject_IMPL));
    NSLog(@"*so sizeof = %lu", sizeof(so));
    NSLog(@"--------------------------------------");
    Person *p = [Person new];
    struct N_Person_IMPL *sp = (__bridge struct N_Person_IMPL *)p;
    NSLog(@"[Person new] sizeof = %lu", sizeof(p));
    NSLog(@"[Person class] sizeof = %lu", sizeof([Person class]));
    NSLog(@"struct N_Person_IMPL sizeof = %lu", sizeof(struct N_Person_IMPL));
    NSLog(@"*sp sizeof = %lu", sizeof(sp));
    NSLog(@"--------------------------------------");
    Student *s = [Student new];
    struct N_Student_IMPL *ss = (__bridge struct N_Student_IMPL *)s;
    NSLog(@"[Student new] sizeof = %lu", sizeof(s));
    NSLog(@"[Student class] sizeof = %lu", sizeof([Student class]));
    NSLog(@"N_Student_IMPL sizeof = %lu", sizeof(struct N_Student_IMPL));
    NSLog(@"Student malloc_size = %zd", malloc_size((__bridge const void*)s));
    NSLog(@"*ss sizeof = %lu", sizeof(ss));
}

執(zhí)行結(jié)果:

打印sizeof

N_NSObject_IMPL葛峻、N_Person_IMPL锹雏、N_Student_IMPL是將OC轉(zhuǎn)成C++代碼時(shí)對(duì)應(yīng)的結(jié)構(gòu),使用sizeof獲取這些結(jié)構(gòu)體大小的時(shí)候术奖,值與class_getInstanceSize的值是一樣的礁遵。

使用sizeof獲取指針、實(shí)例采记、類其結(jié)果都是8佣耐,這又是為什么呢?個(gè)人認(rèn)為傳實(shí)例和類的時(shí)候可以看做傳的其實(shí)就是對(duì)應(yīng)的指針唧龄。指針的值是指針本身存儲(chǔ)的數(shù)值兼砖,這個(gè)值將被編譯器當(dāng)作一個(gè)地址,地址基本就是整形既棺,因此無論用什么類讽挟、對(duì)象作為sizeof的參數(shù)(這里就將sizeof看成是一個(gè)函數(shù)),其結(jié)果都一樣的丸冕。

最后再總結(jié)下class_getInstanceSize耽梅、malloc_sizesizeof的區(qū)別:

  • class_getInstanceSize表示成員變量的所占的內(nèi)存大小
  • malloc_size表示實(shí)際分配的內(nèi)存大小
  • sizeof表示變量或者類型的大小胖烛,傳入結(jié)構(gòu)體眼姐,返回的則是結(jié)構(gòu)的大小诅迷,傳入指針(這里的指針表示C指針,OC的引用)即傳入值妥凳,則返回傳入值的類型大小
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末竟贯,一起剝皮案震驚了整個(gè)濱河市答捕,隨后出現(xiàn)的幾起案子逝钥,更是在濱河造成了極大的恐慌,老刑警劉巖拱镐,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件艘款,死亡現(xiàn)場離奇詭異,居然都是意外死亡沃琅,警方通過查閱死者的電腦和手機(jī)哗咆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來益眉,“玉大人晌柬,你說我怎么就攤上這事」” “怎么了年碘?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長展鸡。 經(jīng)常有香客問我屿衅,道長,這世上最難降的妖魔是什么莹弊? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任涤久,我火速辦了婚禮,結(jié)果婚禮上忍弛,老公的妹妹穿的比我還像新娘响迂。我一直安慰自己,他們只是感情好细疚,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布蔗彤。 她就那樣靜靜地躺著,像睡著了一般惠昔。 火紅的嫁衣襯著肌膚如雪幕与。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天镇防,我揣著相機(jī)與錄音啦鸣,去河邊找鬼。 笑死来氧,一個(gè)胖子當(dāng)著我的面吹牛诫给,可吹牛的內(nèi)容都是我干的香拉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼中狂,長吁一口氣:“原來是場噩夢啊……” “哼凫碌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起胃榕,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤盛险,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后勋又,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苦掘,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年楔壤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鹤啡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蹲嚣,死狀恐怖递瑰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情隙畜,我是刑警寧澤抖部,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站禾蚕,受9級(jí)特大地震影響您朽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜换淆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一哗总、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧倍试,春花似錦讯屈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至躁愿,卻和暖如春叛本,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背彤钟。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國打工来候, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人逸雹。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓营搅,卻偏偏與公主長得像云挟,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子转质,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

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