前言
在探究?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é)果都是一樣的,p和q都是指針類型,指針大小就是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ì)齊原則
數(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在 32 位機(jī)為4字節(jié),則要從4的整數(shù)倍地址開始存儲(chǔ)。結(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ǔ)齊。
內(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) LWStruct1 和 LWStruct2 所包含的變量是一樣的,只是位置不一樣,但是內(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中最大的變量是a占8個(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中最大的變量是a占8個(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)

