內(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)存大小:
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é)果:

通過結(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)存占用大小如下:

舉個(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é)果:

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

首先,元素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é)。

前面的元素略過,最后所占字節(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é)果:

根據(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)問題。