獲取內(nèi)存大小
上一篇我們簡單的提了下內(nèi)存字節(jié)對齊以及為什么要內(nèi)存字節(jié)對齊,那么我們首先看下有什么方式可以獲取內(nèi)存大小。
-
sizeof(type)主要是獲取數(shù)據(jù)類型占用的內(nèi)存大小。
我們先打印下各數(shù)據(jù)類型占用的內(nèi)存情況:
//基本數(shù)據(jù)類型
NSLog(@"char內(nèi)存大小為%lu",sizeof(char));//char:1
NSLog(@"BOOL內(nèi)存大小為%lu",sizeof(BOOL)); // BOOL:1
NSLog(@"short內(nèi)存大小為%lu",sizeof(short)); // short:2
NSLog(@"int內(nèi)存大小%lu",sizeof(int)); // int:4
NSLog(@"float內(nèi)存大小為%lu",sizeof(float)); // float:4
NSLog(@"long內(nèi)存大小為%lu",sizeof(long)); // long:8
NSLog(@"double內(nèi)存大小為%lu",sizeof(double)); // double:8
//結(jié)構(gòu)體類型
struct Struct1{
int age;
}test1; //4
struct Struct2{
int age;
float point;
}test2; //8
struct Struct3{
double d;
int age;
float point;
}test3; //16
NSLog(@"test1內(nèi)存大小為%lu,test2內(nèi)存大小為%lu,test3內(nèi)存大小為%lu,",sizeof(test1),sizeof(test2),sizeof(test3));

-
class_getInstanceSize方法可以獲取對象在堆空間中實(shí)際占用的內(nèi)存情況,只能傳入對象。 -
malloc_size可以獲取實(shí)際為對象開辟的內(nèi)存空間大小情況,只能傳入對象。實(shí)際分配的和實(shí)際占用的內(nèi)存大小并不相同。
內(nèi)存對齊的理解
我們已經(jīng)知道cpu讀取內(nèi)存是以塊為單位進(jìn)行讀取,如果讀取的內(nèi)容正好在一個塊內(nèi),那么一次就能讀取。如果讀取的內(nèi)容分布在了兩個塊,那么就需要至少讀兩次,還要做額為的剔除合并的處理,降低了讀取效率。所以讀取的內(nèi)容的末端可以認(rèn)為這塊內(nèi)容的邊界,那么如何用最少的次數(shù)讀取到內(nèi)容的處理就是內(nèi)存對齊。
內(nèi)存對齊規(guī)則
數(shù)據(jù)成員對齊規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第一個數(shù)據(jù)成員放在offset為0的地方,以后每個數(shù)據(jù)成員存儲的起始位置要從該成員大小或者成員的子成員大小(只要該成員有子成員,比如說是數(shù)組,結(jié)構(gòu)體等)的整數(shù)倍開始(比如int為4字節(jié),則要從4的整數(shù)倍地址開始存儲。
結(jié)構(gòu)體作為成員:如果一個結(jié)構(gòu)里有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從其內(nèi)部最大元素大小的整數(shù)倍地址開始存儲.(struct a里存有struct b,b里有int ,double等元素,那b應(yīng)該從8的整數(shù)倍開始存儲)。
補(bǔ)充:結(jié)構(gòu)體的總大小,也就是sizeof的結(jié)果,必須是其內(nèi)部最大成員的整數(shù)倍,不足的要補(bǔ)齊。
總之,內(nèi)存對齊原則就是:min(m,n) //m為開始的位置,n為所占位數(shù)。當(dāng)m是n的整數(shù)倍時,條件滿足;否則m位空余,繼續(xù)取下一位m+1,繼續(xù)min算法:

結(jié)構(gòu)體對齊
下面我們通過結(jié)構(gòu)體對齊的例子來理解內(nèi)存對齊原則。
定義兩個結(jié)構(gòu)體:
struct OCStruct1{
double a; //8 0-7
char b; //1 8
int c; //4 12-15
short d; //2 16 17
}struct1; 結(jié)構(gòu)體內(nèi)存大小為24
struct OCStruct2{
double a; //8 0-7
int c; //4 8-11
char b; //1 12
short d; //2 14-15
}struct2; 結(jié)構(gòu)體內(nèi)存大小為16
NSLog(@"struct1=%lu, struct2=%lu",sizeof(test1),sizeof(test2));
打印后可以看出struct1內(nèi)存大小為24,struct2內(nèi)存大小為16。擁有相同的元素,內(nèi)存大小卻不一樣,為什么呢?我們根據(jù)內(nèi)存對齊原則來進(jìn)行計(jì)算struct1的內(nèi)存大小:
- 先存放
double a,占8個字節(jié),根據(jù)min(0,8),從0開始存放8個字節(jié),即0-7位存放a。 - 然后存放
char b,占1個字節(jié),起始位置為8,根據(jù)min(8,1),從9開始存放1個字節(jié),即8存放b。 - 然后存放
int c,占4個字節(jié),起始位置為9,根據(jù)min(9,4),4不能被9整除,依次往后找,找到12,從12開始存放4個字節(jié),即12-15存放c。 - 最后存放
short c,占2個字節(jié),起始位置為16,根據(jù)min(16,2),從16開始存放2個字節(jié),即16-17存放d。 - 最后根據(jù)補(bǔ)充原則,占用內(nèi)存總大小必須是最大變量占用的內(nèi)存大小的整數(shù)倍,在此例子里即必須是8的倍數(shù),所以內(nèi)存大小最終確定為24。
同理計(jì)算struct2的內(nèi)存大小:
- 先存放
double a,占8個字節(jié),根據(jù)min(0,8),從0開始存放8個字節(jié),即0-7位存放a。 - 然后存放
int c,占4個字節(jié),起始位置為8,根據(jù)min(8,4),從8開始存放4個字節(jié),即8-11存放'c'。 - 然后存放
char b,占1個字節(jié),起始位置為12,根據(jù)min(12,1),從12開始存放1個字節(jié),即12存放'b'。 - 最后存放
short d,占2個字節(jié)d,起始位置為13,根據(jù)min(16,2),2不能被13整除,繼續(xù)往后查找,從14開始存放2個字節(jié),即14-15存放'd'。 - 最后根據(jù)補(bǔ)充原則,占用內(nèi)存總大小必須是最大變量占用的內(nèi)存大小的整數(shù)倍,在此例子里即必須是8的倍數(shù),所以內(nèi)存大小最終確定為16。
內(nèi)存優(yōu)化
從上面的例子我們可以看出結(jié)構(gòu)體的內(nèi)存大小和結(jié)構(gòu)體內(nèi)成員的順序是有關(guān)系的,根據(jù)這點(diǎn)我們在寫結(jié)構(gòu)體的時候可以進(jìn)行屬性重排,內(nèi)存優(yōu)化。
結(jié)構(gòu)體成員按照內(nèi)存從大到小的順序排列的話,只需要增加少量的內(nèi)存空余就可以完成內(nèi)存對齊。所以內(nèi)存對齊并不是一味的追求用空間換時間,能優(yōu)化的空間還是最好優(yōu)化的。
結(jié)構(gòu)體嵌套的內(nèi)存大小
下面我們根據(jù)內(nèi)存對齊原則看下結(jié)構(gòu)體嵌套結(jié)構(gòu)體的情況:
struct OCStruct3{
char b;
int c;
double a;
short d;
}struct3;
struct OCStruct4{
double a;
int c;
char b;
short d;
struct OCStruct3 stuct3;
}struct4;
NSLog(@"struct3內(nèi)存大小為%lu,struct4內(nèi)存大小為%lu",sizeof(struct3),sizeof(struct4));
打印后的struct4內(nèi)存大小為40,現(xiàn)在我們計(jì)算下是不是40:
結(jié)構(gòu)體的嵌套等同于將從子結(jié)構(gòu)體中將成員變量直接放到父成員變量中:
struct OCStruct4{
double a;
int c;
char b;
short d;
struct OCStruct3{
char b;
int c;
double a;
short d;
}struct3;
}struct4;
- 先存放
double a,占8個字節(jié),根據(jù)min(0,8),從0開始存放8個字節(jié),即0-7位存放a。 - 然后存放
int c,占4個字節(jié),起始位置為8,根據(jù)min(8,4),從8開始存放4個字節(jié),即8-11存放'c'。 - 然后存放
char b,占1個字節(jié),起始位置為12,根據(jù)min(12,1),從12開始存放1個字節(jié),即12存放'b'。 - 然后存放
short d,占2個字節(jié)d,起始位置為13,根據(jù)min(16,2),2不能被13整除,繼續(xù)往后查找,從14開始存放2個字節(jié),即14-15存放'd'。 - 然后存放結(jié)構(gòu)體
struct3里的char b,占1個字節(jié),根據(jù)內(nèi)存對齊原則第二條必須從結(jié)構(gòu)體內(nèi)最大元素內(nèi)存大小的整數(shù)倍,所以應(yīng)該是8的倍數(shù),而16正好被8整除,所以從16開始存放1個字節(jié),即16存放struct3里的b。 - 然后存放結(jié)構(gòu)體
struct3里的int c,占4個字節(jié),起始位置為17,根據(jù)min(17,4),17不能被4整除,往后查找,從20開始存放4個字節(jié),即20-23存放struct3里的c。 - 然后存放結(jié)構(gòu)體
struct3里的double a,占8個字節(jié),起始位置為24,根據(jù)min(24,8),從24開始存放8個字節(jié),即24-31存放struct3里的a。 - 然后存放結(jié)構(gòu)體
struct3里的short d,占2個字節(jié),起始位置為32,根據(jù)min(32,2),從32開始存放2個字節(jié),即32-33存放struct3里的d。 - 最后根據(jù)補(bǔ)充原則,占用內(nèi)存總大小必須是最大變量占用的內(nèi)存大小的整數(shù)倍,在此例子里即必須是8的倍數(shù),所以內(nèi)存大小最終確定為40。
總之,內(nèi)存對齊的作用不僅是便于cpu快速訪問,同時合理的利用內(nèi)存對齊可以有效地節(jié)省存儲空間。