1捐川、結(jié)構(gòu)體內(nèi)存對齊
結(jié)構(gòu)體對齊規(guī)則:
1:數(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ù)倍地址開始存儲揭芍。 min(當(dāng)前開始的位置m n) m = 9 n = 4 9 10 11 12
2:結(jié)構(gòu)體作為成員:如果?個結(jié)構(gòu)?有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從其內(nèi)部最?元素??的整數(shù)倍地址開始存儲.(struct a?存有struct b,b?有char,int ,double等元素,那b應(yīng)該從8的整數(shù)倍開始存儲.)
3:收尾?作:結(jié)構(gòu)體的總??,也就是sizeof的結(jié)果,.必須是其內(nèi)部最?成員的整數(shù)倍.不?的要補(bǔ)?胳搞。
根據(jù)上面規(guī)則,我們來進(jìn)行幾道練習(xí)題:
struct LGStruct1 {
int a; // [0-3]
char b; // [4]
int c; // 5 6 7 [8 9 10 11]
short d; // [12 13]
}struct1;
//內(nèi)部最大是int称杨,也就是4位肌毅,所以需要4字節(jié)對齊, struct1就是16字節(jié)
//同理:struct2的內(nèi)存對齊計算
struct LGStruct2 {
int a; // [0-3]
int b; // [4 - 7]
char c; // [8]
short d; // 9 [10 11]
}struct2;
//內(nèi)部最大是int姑原,也就是4位悬而,所以需要4字節(jié)對齊, struct1就是12字節(jié)
//同理:struct3的內(nèi)存對齊計算
struct LGStruct3 {
double a; // [0-7]
int e; // [8 9 10 11]
struct LGStruct1 str; //最大元素是int锭汛,所以相當(dāng)于4位倍數(shù)開始存儲笨奠,[12 - 28]
}struct3;
//內(nèi)部最大元素是double袭蝗,所以需要8字節(jié)對齊, struct3 就是 32字節(jié)
struct LGStruct4 {
char a; // [0]
struct LGStruct3 str; //最大元素是double般婆,8的倍數(shù)開始存儲到腥,[8 - 39]
}struct4;
//內(nèi)部最大元素是LGStruct3,其內(nèi)部最大是8位蔚袍, struct4 就是 40字節(jié)
做完上面練習(xí)題乡范,對結(jié)構(gòu)體的內(nèi)存規(guī)則應(yīng)該是比較熟悉了,這里發(fā)現(xiàn)了一個奇怪的現(xiàn)象页响,也就是struct1和struct2存儲的內(nèi)容是一樣多的篓足,但是大小卻不一樣。之前探索了Objective-C中的class和對象在C++中的原理實現(xiàn)知道對象存儲是一個結(jié)構(gòu)體闰蚕,存儲方式如:
struct Persion_2415_IMPL { //存儲Persion的數(shù)據(jù)是一個struct Persion_2415_IMPL的數(shù)據(jù)結(jié)構(gòu)
struct NSObject_IMPL NSObject_IVARS;
int _age_2415;
NSString *_name_2415;
};
從上面存儲的結(jié)構(gòu)可以猜想栈拖,存儲內(nèi)存大小很可能和成員方法、類方法沒有關(guān)系没陡。測試之后發(fā)現(xiàn)涩哟,果然是沒有關(guān)系的。
那么考慮我們oc的對象使用的內(nèi)存盼玄,是否和變量的順序有關(guān)系呢贴彼,進(jìn)行如下測試
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
@interface Person : NSObject
@end
@implementation Person
{
//isa
double a; //[8-15]
char b; //[16]
double c; //[24 - 31]
char d; //[32]
double e; // [40 - 47]
char f; //[48]
double g; // [56 - 63]
char h; // [64]
}
@end
@interface Student : NSObject
@end
@implementation Student
{
//isa
double a; //[8-15]
double b; //[16-23]
double c; //[24-31]
double d; //[32-40]
char e; // [41]
char f; // [42]
char g; // [43]
char h; // [44]
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person * per = [[Person alloc] init];
Student * stu = [[Student alloc] init];
NSLog(@"class_getInstanceSize -- %ld %ld",class_getInstanceSize([Person class]), class_getInstanceSize([Student class]));
NSLog(@"malloc_size -- %ld %ld",malloc_size((__bridge void*)(per)), malloc_size((__bridge void*)(stu)));
}
return 0;
}
/*
輸出結(jié)果:
2021-06-17 16:47:19.435118+0800 004-isa分析[8791:160576] class_getInstanceSize -- 72 48
2021-06-17 16:47:19.435456+0800 004-isa分析[8791:160576] malloc_size -- 80 48
*/
根據(jù)上面結(jié)果發(fā)現(xiàn),原來真的會有內(nèi)存使用的差別埃儿,那么我們可以根據(jù)這個方式來優(yōu)化內(nèi)存器仗。再次探索,如果是作為屬性的話童番,蘋果內(nèi)部會不會幫我們做一些優(yōu)化呢精钮,發(fā)現(xiàn)如果把上面的成員變量改成屬性的話
/*
輸出結(jié)果:
2021-06-17 16:29:54.056023+0800 004-isa分析[8613:153735] class_getInstanceSize -- 48 48
2021-06-17 16:29:54.056347+0800 004-isa分析[8613:153735] malloc_size -- 48 48
*/
由此可以知道,蘋果幫我們使用屬性的時候做了內(nèi)存優(yōu)化剃斧,所以我們?nèi)绻暶鞒蓡T變量轨香,一般最好使用屬性的方式,能夠更加節(jié)約內(nèi)存幼东。
2臂容、為什么要進(jìn)行內(nèi)存對齊呢
由于CPU讀取的時候很多是按照雙字節(jié),4字節(jié)讀取的根蟹,如果不進(jìn)行內(nèi)存對齊的話脓杉,例如結(jié)構(gòu)體
struct s1{
char a;
int b;
}
/*
內(nèi)存不對齊的情況下:
char a 存儲在 0 字節(jié)
int b 存儲在 1-4 字節(jié)
需要讀取b的時候,如果是4字節(jié)讀取的简逮,需要讀2次丽已,提取2次,組合一次
0 - 3买决, 4 - 7沛婴, 然后再把 0-3中的1-3提取,和4-8中的4提取督赤,再組合出 1-4
如果內(nèi)存對齊的情況下:
char a 存儲在 0 字節(jié)
int b 存儲在 4-7 字節(jié)
需要讀取b的時候嘁灯,如果是4字節(jié)讀取的,需要讀1次躲舌,
4 - 7 直接讀取成功
這是一種以空間換時間的方式丑婿,能夠很好的提高讀取的效率
*/
3、OC中的內(nèi)存再探索
從剛剛的打印中我們有一個疑問没卸,Person的class_getInstanceSize 和 malloc_size的大小并不一樣羹奉,其中
class_getInstanceSize:對象至少需要的大小
malloc_size: 對象實質(zhì)的大小
class_getInstanceSize的值,我們從結(jié)構(gòu)體對齊中很容易得出原因约计。但是malloc_size為什么會不一樣呢诀拭。我們開始探索OC中alloc時蘋果的源碼流程。之后再增加具體探索流程和代碼閱讀源碼技巧
發(fā)現(xiàn)alloc的時候煤蚌,會在class_getInstanceSize的基礎(chǔ)上進(jìn)行16字節(jié)的對齊耕挨。所以malloc_size大小會不一樣。
總結(jié):
- 我們可以在聲明成員變量的時候尉桩,盡量的使用屬性的方式筒占,這時蘋果會幫我們做好內(nèi)存的優(yōu)化。使得內(nèi)存空間更加節(jié)省
- 對象的成員變量大兄├纭(包括isa)相加盡量是16的整數(shù)倍翰苫,可以減少一些浪費
- 對象的內(nèi)存使用大小和對象方法,類方法都沒有關(guān)系