iOS底層 - 結(jié)構(gòu)體內(nèi)存對(duì)齊

內(nèi)存大小

在工作中,或多或少都會(huì)接觸到內(nèi)存對(duì)齊這個(gè)概念,而內(nèi)存對(duì)齊到底是什么呢,今天來探索一下這個(gè)神秘的東西。話不多說,我們先來看下對(duì)象在內(nèi)存中的存儲(chǔ)。

首先創(chuàng)建一個(gè)類:LGPerson,然后實(shí)例化對(duì)象:

//  創(chuàng)建類
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) float height;
@property (nonatomic, assign) char flag;

@end

并對(duì)其各個(gè)屬性賦值:

#import "LGPerson.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>

//  實(shí)例化并賦值:
LGPerson *person = [LGPerson alloc];
person.name      = @"degulade";
person.nickName  = @"DG";
person.age       = 18;
person.height    = 180.5;
person.flag      = 'a';

查看他們?cè)趦?nèi)存中的存儲(chǔ):

內(nèi)存中的地址:值

查看它們的內(nèi)存大小:

NSLog(@"對(duì)象類型的內(nèi)存大小--%lu",sizeof(person));
NSLog(@"對(duì)象至少需要的內(nèi)存大小--%lu",class_getInstanceSize([person class]));
NSLog(@"系統(tǒng)分配的內(nèi)存大小--%lu",malloc_size((__bridge const void *)(person)));

結(jié)果:

內(nèi)存大小

通過結(jié)果我們可以看到,對(duì)象需要的內(nèi)存大小與系統(tǒng)實(shí)際分配的內(nèi)存大小并不一樣。下面我們先給出結(jié)論:

  • 在 C 語言中,sizeof() 是一個(gè)判斷數(shù)據(jù)類型或者表達(dá)式長(zhǎng)度的運(yùn)算符。sizeof()的計(jì)算發(fā)生在編譯時(shí)刻,所以它可以被當(dāng)作常量表達(dá)式使用.而sizeof(person)打印的是person的類型也就是對(duì)象,本質(zhì)上他是結(jié)構(gòu)體指針
  • class_getInstanceSize打印出的結(jié)果是對(duì)象至少需要的內(nèi)存大小,在這里我們創(chuàng)建的DMPerson類一共有5個(gè)屬性,其中2個(gè)NSString,1個(gè)int,1個(gè)float,1個(gè)char類型。再加上類本身isa需要的8個(gè)字節(jié),因此DMPerson類至少需要8+8+8+4+4+1 = 33然后8字節(jié)對(duì)齊,因此打印輸出是40。
  • malloc_size打印的結(jié)果是系統(tǒng)分配的實(shí)際內(nèi)存大小,前面我們計(jì)算出LGPerson類至少需要40個(gè)字節(jié)才能完整的存儲(chǔ)下來,而在內(nèi)存中對(duì)類的實(shí)際分配是16字節(jié)對(duì)齊,因此最后結(jié)果是48。

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

通過上面的代碼分析,我們了解不同的數(shù)據(jù)類型,在內(nèi)存中占用的大小是不一樣的,各種類型具體的內(nèi)存占用大小如下:

各種數(shù)據(jù)類型所占內(nèi)存的字節(jié)大小

舉個(gè)例子:

struct LGStruct1 {
    double a;       
    char b;         
    int c;          
    short d;        
}struct1;

struct LGStruct2 {
    double a;       
    int b;          
    char c;             
     short d;        
}struct2;

NSLog(@"struct1內(nèi)存大?。?lu,struct2內(nèi)存大小:%lu",sizeof(struct1),sizeof(struct2));

創(chuàng)建如上兩個(gè)結(jié)構(gòu)體,包含了一樣的數(shù)據(jù)元素,但是成員變量的位置不同,所以他們的sizeof(內(nèi)存大小)是否是一樣呢?
打印出結(jié)果:

打印內(nèi)存大小

結(jié)果我們可以看到,兩個(gè)內(nèi)容完全相同的結(jié)構(gòu)體,因?yàn)槌蓡T變量位置的不同,最后sizeof出來的結(jié)果不一樣。但是這是為什么呢,因?yàn)?code>內(nèi)存對(duì)齊,需要遵守內(nèi)容對(duì)齊原則。

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

  1. 數(shù)據(jù)成員對(duì)?規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第?個(gè)數(shù)據(jù)成員放在offset為0的地?,以后每個(gè)數(shù)據(jù)成員存儲(chǔ)的起始位置要從該成員??或者成員的?成員??(只要該成員有?成員,?如說是數(shù)組,結(jié)構(gòu)體等)的整數(shù)倍開始(?如int為4字節(jié),則要從4的整數(shù)倍地址開始存儲(chǔ)。 min(當(dāng)前開始的位置m n) m = 9 n = 4,n:[9 10 11 12]

  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ǔ)?。

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

以上面的結(jié)構(gòu)體struct1和struct2為例,根據(jù)內(nèi)存對(duì)齊原則,我們就可以知道他們的數(shù)據(jù)存儲(chǔ)規(guī)則了:

struct LGStruct1 {
    double a;       // 8    [0 7]
    char b;         // 1    [8]
    int c;          // 4    (9 10 11 [12 13 14 15]
    short d;        // 2    [16 17]       【24】
}struct1;

struct LGStruct2 {
    double a;       // 8    [0 7]
    int b;          // 4    [8 9 10 11]
    char c;         // 1    [12]
    short d;        // 2    (13 [14 15]       【16】
}struct2;

為了更加清晰,我們用圖文形式表現(xiàn)出來:

Struct1內(nèi)存大小

首先,元素a為double,占用字節(jié)為8[0 7];元素b為char,占用字節(jié)為1[8];元素c為int,占用字節(jié)4(9 10 11) [12 13 14 15];元素d為short,占用字節(jié)2[16 17];根據(jù)對(duì)齊原則的收尾要求:必須是內(nèi)部最大成員(a:8)的整數(shù)倍,又因?yàn)?code>2*8 < d的字節(jié)大小,所以LGStruct1等于(3*8)24字節(jié)。

Struct2內(nèi)存大小

前面的元素略過,最后所占字節(jié)數(shù)為16,根據(jù)內(nèi)存對(duì)齊原則的收尾要求,16字節(jié)為其內(nèi)部最大成員double a所占空間8字節(jié)的整數(shù)倍,所以等于16字節(jié)。

再結(jié)合之前的打印結(jié)果,證明了系統(tǒng)確實(shí)是按照這個(gè)原則進(jìn)行的數(shù)據(jù)存儲(chǔ)。

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

舉個(gè)例子:

struct LGStruct1 {
    double a;       // 8    [0 7]
    char b;         // 1    [8]
    int c;          // 4    (9 10 11 [12 13 14 15]
    short d;        // 2    [16 17] 【24】
}struct1;

struct LGStruct2 {
    double a;       // 8    [0 7]
    int b;          // 4    [8 9 10 11]
    char c;         // 1    [12]
    short d;        // 2    (13 [14 15] 【16】
}struct2;

// 家庭作業(yè) : 結(jié)構(gòu)體內(nèi)存對(duì)齊
struct LGStruct3 {
    double a;     //  8     [0...7]
    int b;        //  4    [8 9 10 11]
    char c;       //  1     [12]
    short d;      //  2     (13 [14 15]
    int e;        //  4   (16 [17 18 19 20] 【 24】
    struct LGStruct1 str1;  //   【24】 + 24
    struct LGStruct2 str2;  //   【16】 + 48

}struct3;
        
NSLog(@"struct3內(nèi)存大?。?lu",sizeof(struct3));

我們首先看看系統(tǒng)計(jì)算的結(jié)果:

打印內(nèi)存大小

根據(jù)對(duì)齊原則,如圖所示:


由于struct LGStruct1 str也是一個(gè)結(jié)構(gòu)體,因此其內(nèi)部也需要進(jìn)行字節(jié)對(duì)齊,str1所占的字節(jié)數(shù)是24字節(jié),按照內(nèi)存對(duì)齊原則的第二條,str需要從24的位置開始存儲(chǔ)占用24個(gè)字節(jié),占用字節(jié)數(shù)為48字節(jié)。最后的struct LGStruct2 str2,,str2所占的字節(jié)數(shù)是16字節(jié),從48字節(jié)開始,占用為64字節(jié);且滿足第三條原則,所以最終占用字節(jié)數(shù)為(8*8)64字節(jié)。

總結(jié)以上經(jīng)驗(yàn),我們可以得出結(jié)論:結(jié)構(gòu)體中的結(jié)構(gòu)體,也是以整體的形式進(jìn)行存儲(chǔ)的,其內(nèi)部也需要進(jìn)行內(nèi)存對(duì)齊。

至此,我們可以得出對(duì)于結(jié)構(gòu)體,系統(tǒng)確實(shí)是按照內(nèi)存對(duì)齊原則來進(jìn)行存儲(chǔ)數(shù)據(jù)的。
在iOS中,對(duì)象的本質(zhì)實(shí)際上就是結(jié)構(gòu)體,是不是能用結(jié)構(gòu)體內(nèi)存對(duì)齊的理論來驗(yàn)證呢,我們繼續(xù)往下探索:

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

我們修改一下最初LGPerson類的代碼,修改它的屬性的位置:

@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) float height;
@property (nonatomic, assign) char flag;
@property (nonatomic, assign) int age;

賦的值不變,然后打印內(nèi)存地址:

輸出結(jié)果:

實(shí)際上系統(tǒng)在對(duì)象層面已經(jīng)對(duì)內(nèi)存對(duì)齊進(jìn)行了優(yōu)化,不管你屬性是怎么樣排列的,系統(tǒng)都會(huì)按照最優(yōu)的順序,將數(shù)據(jù)存儲(chǔ)到內(nèi)存中去。

總結(jié)

系統(tǒng)的數(shù)據(jù)存儲(chǔ)都是根據(jù)內(nèi)存對(duì)齊原則來進(jìn)行對(duì)齊的,對(duì)于結(jié)構(gòu)體,可能會(huì)因?yàn)槌蓡T順序的問題,造成一定的內(nèi)存空間浪費(fèi),而對(duì)于對(duì)象來說,在iOS的系統(tǒng)層面已經(jīng)幫我們進(jìn)行了內(nèi)存優(yōu)化,保證了最優(yōu)的存儲(chǔ)方式。因此,在開發(fā)過程中,如果使用到了結(jié)構(gòu)體類型,需要特別注意內(nèi)存對(duì)齊的相關(guān)問題。

?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

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