對象是如何分配內存的塌鸯?對象是如何計算內存大小的呢岸蜗?對象內存分配跟什么有關?
代碼分析
sizeof() 計算一個變量或者類型的大兄刈铩(以字節(jié)為單位)
class_getInstanceSize 計算對象所需要的內存大小,結算結果遵循8字節(jié)對齊努隙,其實現(xiàn)源碼如下:
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
malloc_size 計算對象的實際大小,使用時需要引入頭文件
//沒有添加任何屬性
@interface LNPerson : NSObject
@end
@implementation LNPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [LNPerson alloc];
NSLog(@"%lu - %lu - %lu",sizeof(person),class_getInstanceSize([LNPerson class]),malloc_size((__bridge const void *)(person)));
}
return 0;
}
打印結果:8 - 8 - 16
//添加一個屬性
@interface LNPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation LNPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
LNPerson *person = [LNPerson alloc];
NSLog(@"打印結果: %lu - %lu - %lu",sizeof(person),class_getInstanceSize([LNPerson class]),malloc_size((__bridge const void *)(person)));
}
return 0;
}
打印結果: 8 - 16 - 16
//添加兩個屬性
@interface LNPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@end
@implementation LNPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
LNPerson *person = [LNPerson alloc];
NSLog(@"打印結果: %lu - %lu - %lu",sizeof(person),class_getInstanceSize([LNPerson class]),malloc_size((__bridge const void *)(person)));
}
return 0;
}
打印結果: 8 - 24 - 32
這里先附上一張數(shù)據(jù)類型占用內存大小的表格:
由以上三分代碼運行結果可以知道對象的內存大小跟屬性有關(實際上是跟實例變量有關)球恤。代碼分析如下:
- 這里面sizeof的結果一直沒變化,實際上他只是計算person這個指針的大小剃法,而這個指針是一直不變的碎捺,在OC里面對象指針的大小為8字節(jié)(其他數(shù)據(jù)類型占用內存大小可以參考圖1-1)路鹰。
- 通過class_getInstanceSize打印結果可以看出隨著屬性的增多贷洲,開辟一個對象所需要的內存也在增加(這里面需要注意的是屬性age是int屬性收厨,如果按照對象所需要內存大小計算應該是8+8(name屬性大小)+ 4(age)= 20,可是為什么結果卻是24呢优构?實際上這是底層結構體的內存對齊遵循了8字節(jié)對齊的計算結果诵叁,關于結構體內存對齊可以參考相關資料。)钦椭;
- malloc_size計算的是對象的實際內存大小拧额,我們看到,這跟class_getInstanceSize計算出來的大小不一致彪腔,這實際上是因為OC對象內存分配時遵循了16字節(jié)對齊原則侥锦,當對象所需內存小于16字節(jié)時,還是會分配16字節(jié)德挣;當對象所需內存大于16字節(jié)且大小剛好為16的倍數(shù)時恭垦,則按照所需大小分配;但是當對象所需內存大于16字節(jié)且大小不是16的倍數(shù)時格嗅,則分配大于所需內存大小的最小的16的倍數(shù)番挺。
什么是內存對齊
內存對齊”應該是編譯器的“管轄范圍”。編譯器為程序中的每個“數(shù)據(jù)單元”安排在適當?shù)奈恢蒙贤鸵础却鎸R意味將數(shù)據(jù)類型寫入到內存地址時是按照它們大小切割的玄柏。簡單說就是如果內存地址是n字節(jié)的倍數(shù),那么我們說這n字節(jié)是內存對齊的贴铜,注意粪摘,這里n是2的冪,說白了绍坝,內存地址正好放下n字節(jié)的倍數(shù)赶熟,兩者相除余數(shù)為零,正好整除陷嘴。例如映砖,從上面的代碼分析可以看出對象person所需要的實際大小為24字節(jié),而實際大小確是32字節(jié)灾挨,這是因為OC對象的內存是16字節(jié)對齊的邑退。
為什么需要內存對齊
那編譯器為什么要進行內存對齊呢?主要基于以下兩個原因:
- 1劳澄、平臺原因(移植原因):
不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù)的地技;某些硬件平臺只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常秒拔。 - 2莫矗、性能原因
數(shù)據(jù)結構(尤其是棧)應該盡可能地在自然邊界上對齊。原因在于,為了訪問未對齊的內存作谚,處理器需要作兩次內存訪問三娩;而對齊的內存訪問僅需要一次訪問。
內存對齊遵循什么樣的原則
1妹懒、數(shù)據(jù)成員對齊規(guī)則:結構(struct)(或聯(lián)合(union))的數(shù)據(jù)成員雀监,第一個數(shù)據(jù)成員放在offset為0的地方,以后每個數(shù)據(jù)成員存儲的起始位置要從該成員大小或者成員的子成員大姓;!(只要成員有子成員会前,比如說數(shù)組,結構體等)的整數(shù)倍開始(比如int為4字節(jié)匾竿,則要從4的整數(shù)倍地址開始存儲瓦宜。min(當前開始的位置m n))m = 9, n= 4:9 10 11 12 ,12是4的整數(shù)倍岭妖,所以從12開始存儲
2临庇、結構體作為成員:如果一個結構體里有些結構體成員,則結構體成員要從其內部最大元素的整數(shù)倍地址開始存儲区转。(struct a里面存儲struct b苔巨, b里有char、int废离、 double等元素侄泽,那b應該從8的整數(shù)倍開始存儲。)
3蜻韭、收尾工作:結構體的總大小悼尾,也就是sizeof的結果,必須是其內部最大成員的整數(shù)倍肖方,不足的要補齊闺魏。
舉個例子:
// 64位
struct MyStruct1 {
double a;//8字節(jié) a存儲為0~7位置共8位
char b;//1字節(jié) 8是1的整數(shù)倍,所以b從8位置開始存儲俯画,共1位
int c;// 4字節(jié) 前8位已被占用析桥,9不是4的倍數(shù),所以得往前找最近的4的倍數(shù)12艰垂,從12位開始存儲泡仗,共四位12~15
short d;//2字節(jié) 16位剛好是2的倍數(shù),從16位開始存儲猜憎,共兩位16~17娩怎,可以推出struct1的所需大小為0~17共18位
}struct1;// 但是結構大小是其內部最大成員(這里的a,8字節(jié))的整數(shù)倍胰柑,因此最小為24
struct MyStruct2 {// 同上面的推理
double a;//8 0~7
int c;// 4 8是4的倍數(shù) 8~11
char b;//1 12
short d;//2 13不是2的整數(shù)倍截亦, 故14~15爬泥,所需大小位0~15共16位,實際大小應該為16
}struct2;
struct MyStruct3 {// 同上面的推理
double a;//8 0~7
int c;// 4 8是4的倍數(shù) 8~11
char b;//1 12
short d;//2 13不是2的整數(shù)倍崩瓤, 故14~15袍啡,所需大小15
//如果一個結構體里有些結構體成員,則結構體成員要從其內部最大元素的整數(shù)倍地址開始存儲谷遂。所以雖然這里myStruct2的大小為16字節(jié)葬馋,但是依然以8字節(jié)為倍數(shù)開始計算卖鲤,這里16剛好是8的整數(shù)倍肾扰,所以myStruct2從16位開始,大小16位蛋逾,所以應該是16~31位集晚,總共大小32位
struct MyStruct2 myStruct2;//16
}struct3;// 32
打印這三個結構體:
NSLog(@"struct1大小:%ld",sizeof(struct1));
NSLog(@"struct2大星弧:%ld",sizeof(struct2));
NSLog(@"struct3大型蛋巍:%ld",sizeof(struct3));
打印結果:
2021-07-25 17:12:57.383194+0800 MemoryAlignmentTest[8346:636413] struct1大小:24
2021-07-25 17:12:57.383265+0800 MemoryAlignmentTest[8346:636413] struct2大锌鞴场:16
2021-07-25 17:12:57.383295+0800 MemoryAlignmentTest[8346:636413] struct3大辛隆:32
結果是符合規(guī)則的。
備注:OC對象的底層結構是結構體姑丑,結構體大小內存分配遵循8字節(jié)對齊原則蛤签。但是OC對象在結構8字節(jié)對齊計算出的內存大小的基礎上遵循16字節(jié)對齊原則。這么做應該是出于兼容性栅哀、容錯的等方面的考慮震肮,給對象留點空間,避免擠滿留拾。