IOS 底層原理之內(nèi)存對(duì)齊

前言

在探究?jī)?nèi)存對(duì)齊之前,我們先了解下計(jì)算內(nèi)存大小的三種方式,因?yàn)榻酉聛碓谔接憙?nèi)存對(duì)齊時(shí)候,我們需要用到其中的方法,首先定義一個(gè)LWPweson類(沒有自定義的屬性和變量)

     LWPerson * p = [LWPerson alloc];
     LWPerson * q;
        
     NSLog(@"對(duì)象類型占用內(nèi)存大小--%lu",sizeof(p));
     NSLog(@"對(duì)象類型占用內(nèi)存大小--%lu",sizeof(q));
     NSLog(@"對(duì)象實(shí)際內(nèi)存大小-----%lu",class_getInstanceSize([p class]));
     NSLog(@"對(duì)象實(shí)際內(nèi)存大小-----%lu",class_getInstanceSize([q class]));
     NSLog(@"系統(tǒng)為分配的內(nèi)存大小--%lu",malloc_size((__bridge const void *)(p)));
     NSLog(@"系統(tǒng)為分配的內(nèi)存大小--%lu",malloc_size((__bridge const void *)(q)));

打印結(jié)果如下

     2020-09-19 22:59:42.507118+0800 KCObjcTest[4003:298596] 對(duì)象類型占用內(nèi)存大小--8
     2020-09-19 22:59:42.507678+0800 KCObjcTest[4003:298596] 對(duì)象類型占用內(nèi)存大小--8
     2020-09-19 22:59:42.507780+0800 KCObjcTest[4003:298596] 對(duì)象實(shí)際內(nèi)存大小-----8
     2020-09-19 22:59:42.507844+0800 KCObjcTest[4003:298596] 對(duì)象實(shí)際內(nèi)存大小-----0
     2020-09-19 22:59:42.507910+0800 KCObjcTest[4003:298596] 系統(tǒng)分配的內(nèi)存大小--16
     2020-09-19 22:59:42.507963+0800 KCObjcTest[4003:298596] 系統(tǒng)分配的內(nèi)存大小--0

結(jié)果

  • sizeof 傳進(jìn)來的是類型,用來計(jì)算這個(gè)類型占多大內(nèi)存,這個(gè)在編譯器編譯階段就會(huì)確定,所以sizeof(p)sizeof(q)的結(jié)果都是一樣的,pq都是指針類型,指針大小就是8個(gè)字節(jié)。
  • class_getInstanceSize對(duì)象的實(shí)際內(nèi)存大小,大小由類的屬性和變量來決定,實(shí)際上并不是嚴(yán)格意義上的對(duì)象內(nèi)存大小,因?yàn)榈讓舆M(jìn)行8字節(jié)對(duì)齊算法define WORD_MASK 7UL ((x + WORD_MASK) & ~WORD_MASK ,LWPerson類中沒有其他的屬性和變量,但是繼承了NSObject,NSObject中有一個(gè)isa指針,所以內(nèi)存大小是8字節(jié)
  • malloc_size系統(tǒng)分配的內(nèi)存大小是按16字節(jié)對(duì)齊的方式,即是按16的倍數(shù)分配 ,不足則系統(tǒng)會(huì)自動(dòng)填充字節(jié)(具體的calloc詳細(xì)流程后續(xù)會(huì)更新)

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

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

  1. 數(shù)據(jù)成員對(duì)齊規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第一個(gè)數(shù)據(jù)成員放在offset0的地方,以后每個(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ǔ)齊。

內(nèi)存對(duì)齊原則描述的有點(diǎn)多有點(diǎn)復(fù)雜,下面會(huì)用實(shí)例進(jìn)行詳細(xì)的說明

各類型所占字節(jié)

結(jié)構(gòu)體對(duì)齊

對(duì)象的本質(zhì)就是結(jié)構(gòu)體(對(duì)象在底層編譯成結(jié)構(gòu)體),結(jié)構(gòu)體對(duì)齊實(shí)際上可以看做是內(nèi)存對(duì)齊,只不過對(duì)象可以進(jìn)行內(nèi)存優(yōu)化,接下來,我們用實(shí)例進(jìn)行探究結(jié)構(gòu)體對(duì)齊

struct LWStruct1{
    long    a; // 8
    int     b; // 4
    short   c; // 2
    char    d; // 1
}LWStruct1;


struct LWStruct2{
    long    a; // 8
    char    d; // 1
    int     b; // 4
    short   c; // 2
 
}LWStruct2;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"-----%lu------%lu",sizeof(LWStruct1),sizeof(LWStruct2));
    }
    return 0;
}
2020-09-20 15:29:13.535102+0800 KCObjcTest[1632:89696] -----16------24

從打印結(jié)果我們發(fā)現(xiàn) LWStruct1LWStruct2 所包含的變量是一樣的,只是位置不一樣,但是內(nèi)存大小不一樣,為什么? 其實(shí)這就是我們一直說的內(nèi)存對(duì)齊

下面我們就根據(jù)內(nèi)存對(duì)齊原則來進(jìn)行簡(jiǎn)單的分析和計(jì)算
LWStruct1內(nèi)存大小的詳細(xì)過程

  • 變量a: 占8個(gè)字節(jié),從0開始,min(0,8),即0 ~ 7 存儲(chǔ)a
  • 變量b: 占4個(gè)字節(jié),從8開始,min(8,4),即8 ~ 11 存儲(chǔ)b
  • 變量c: 占2個(gè)字節(jié),從12開始,min(12,2),即12~ 13 存儲(chǔ)c
  • 變量d: 占1個(gè)字節(jié),從14開始,min(14,1),即14存儲(chǔ)d
    因此LWStruct1的內(nèi)存大小是15字節(jié),而LWStruct1中最大的變量是a8個(gè)字節(jié),所以LWStruct1需要實(shí)際內(nèi)存必須是8的倍數(shù)15字節(jié)不是8的倍數(shù),所以系統(tǒng)自動(dòng)填充成16字節(jié),最終sizeof(LWStruct1)的大小是16
    LWStruct1解析圖如下

LWStruct2內(nèi)存大小的詳細(xì)過程

  • 變量a: 占8個(gè)字節(jié),從0開始,min(0,8),即0 ~ 7 存儲(chǔ)a
  • 變量d: 占1個(gè)字節(jié),從8開始,min(8,1),即8 存儲(chǔ)d
  • 變量b: 占4個(gè)字節(jié),從9開始,min(9,4),9 % 4 != 0,繼續(xù)往后移動(dòng)直到找到可以整除4的位置 12,min(12,4),即12 ~ 15 存儲(chǔ)b
  • 變量c: 占2個(gè)字節(jié),從16開始,min(16,2),即16 ~ 17存儲(chǔ)c
    因此LWStruct2的內(nèi)存大小是18字節(jié),而LWStruct2中最大的變量是a8個(gè)字節(jié),所以LWStruct2需要實(shí)際內(nèi)存必須是8的倍數(shù),18字節(jié)不是8的倍數(shù),所以系統(tǒng)自動(dòng)填充成24字節(jié),最終sizeof(LWStruct2)的大小是24
    LWStruct2解析圖如下

結(jié)構(gòu)體中嵌套結(jié)構(gòu)體

繼續(xù)用實(shí)例探究

struct LWStruct2{
    long    a; // 8
    char    d; // 1
    int     b; // 4
    short   c; // 2
 
}LWStruct2;

struct LWStruct3{
    long    a; // 8
    int     b; // 4
    short   c; // 2
    char    d; // 1
    struct LWStruct2 lwStr;
}LWStruct3;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"-----%lu------%lu",sizeof(LWStruct2),sizeof(LWStruct3));
    }
    return 0;
}
2020-09-20 17:07:41.507870+0800 KCObjcTest[2467:159943] -----24------40

LWStruct3內(nèi)存大小的詳細(xì)過程

  • 變量a: 占8個(gè)字節(jié),從0開始,min(0,8),即0 ~ 7 存儲(chǔ)a
  • 變量b: 占4個(gè)字節(jié),從8開始,min(8,4),即8 ~ 11 存儲(chǔ)b
  • 變量c: 占2個(gè)字節(jié),從12開始,min(12,2),即12~ 13 存儲(chǔ)b
  • 變量d: 占1個(gè)字節(jié),從14開始,min(14,1),即14存儲(chǔ)d
  • 變量lwStr: 結(jié)構(gòu)體變量lwStr,根據(jù)內(nèi)存對(duì)齊原則結(jié)構(gòu)體成員要從其內(nèi)部最大元素大小的整數(shù)倍地址開始存儲(chǔ),LWStruct2中最大的變量是8,所以從16位置開始存儲(chǔ),即LWStruct2存儲(chǔ) 16-33位置,

因此LWStruct3的內(nèi)存大小是34字節(jié),而MyStruct3中最大變量為 lwStr, 其最大成員內(nèi)存字節(jié)數(shù)為8,所以LWStruct13內(nèi)存必須是8的倍數(shù),34字節(jié)不是8的倍數(shù),所以系統(tǒng)自動(dòng)填充成40字節(jié),最終sizeof(LWStruct3)的大小是40
LWStruct3解析圖如下
LWStruct3中的 lwStr任然按照LWStruct2排列,不在詳細(xì)排列

內(nèi)存優(yōu)化

下面通過實(shí)例來看下內(nèi)存優(yōu)化是怎么優(yōu)化的

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LWPerson * p  = [LWPerson alloc];
        p.nickName = @"hello";
        p.age = 18;
        p.height = 183;
        
    }
    return 0;
}

打印結(jié)果如下


下面我們?cè)?LWPerson添加兩個(gè)屬性,繼續(xù)探究

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LWPerson * p  = [LWPerson alloc];
        p.nickName = @"hello";
        p.age = 18;
        p.height = 183;
        p.a = 'a';
        p.b = 'b';
    }
    return 0;
}

打印結(jié)果如下


通過lldb斷點(diǎn)打印可以看出 ,age的讀取通過 0x00000012 ,a的讀取通過0x61(a的ASCII碼是97),b的讀取通過0x62(b的ASCII碼是98)
我們神奇的發(fā)現(xiàn) int age,char a,char b,共用了一個(gè)8字節(jié)內(nèi)存空間,而且對(duì)象的屬性或者變量存儲(chǔ)順序和結(jié)構(gòu)體的也不一樣,這就是內(nèi)存優(yōu)化

總結(jié)

其實(shí)內(nèi)存對(duì)齊只是制定了一套規(guī)則,目的是提高cpu的讀取效率和安全的訪問,通過字節(jié)對(duì)齊雖然這樣浪費(fèi)了大量的內(nèi)存,但是同時(shí)又進(jìn)行內(nèi)存優(yōu)化盡可能的降低了內(nèi)存了浪費(fèi)

最后編輯于
?著作權(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ù)。

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