什么是內(nèi)存對齊
元素是按照定義順序一個一個放到內(nèi)存中去的零截,但并不是緊密排列的。從結(jié)構(gòu)體存儲的首地址開始休蟹,每個元素放置到內(nèi)存中時棍苹,它都會認(rèn)為內(nèi)存是按照自己的大形匏蕖(通常它為4或8)來劃分的,因此元素放置的位置一定會在自己寬度的整數(shù)倍上開始廊勃,這就是所謂的內(nèi)存對齊。
編譯器為程序中的每個“數(shù)據(jù)單元”安排在適當(dāng)?shù)奈恢蒙暇选語言允許你干預(yù)“內(nèi)存對齊”坡垫。如果你想了解更加底層的秘密,“內(nèi)存對齊”對你就不應(yīng)該再模糊了画侣。
下圖是結(jié)構(gòu)體在32bit和64bit環(huán)境下各基本數(shù)據(jù)類型所占的字節(jié)數(shù):
結(jié)構(gòu)體內(nèi)存對齊
接下來我們定義兩個struct冰悠,通過打印它們的sizeof()來探索一下其對齊的規(guī)律
struct LGHStruct01 {
long a;
char b;
double c;
int d;
short e;
}struct01;
struct LGHStruct02 {
long a;
double b;
int c;
short d;
char e;
}struct02;
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
appDelegateClassName = NSStringFromClass([AppDelegate class]);
NSLog(@"%lu-%lu",sizeof(struct01),sizeof(struct02));
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
// 打印的結(jié)果是: 32-24
我們可以看到兩個結(jié)構(gòu)體其中定義的變量
以及變量類型
都是一致的
,唯一的區(qū)別是在于定義變量的順序
不一致配乱,為什么struct01溉卓,struct02所占的內(nèi)存大小一個是32一個是24呢皮迟?接下來我們來了解一下內(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ù)倍地址開始存
儲爆阶。 min(當(dāng)前開始的位置m n) m = 9 n = 4 (9 10 11 12) - 原則二:結(jié)構(gòu)體作為成員:如果?個結(jié)構(gòu)?有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從其內(nèi)部最?元素??的整數(shù)倍地址開始存儲.(struct struct03?存有struct struct01,struct01?有l(wèi)ong,char,int ,double等元素,那struct01應(yīng)該從8的整數(shù)倍開始存儲.)
- 原則三:收尾?作:結(jié)構(gòu)體的總??,也就是sizeof的結(jié)果,.必須是當(dāng)前結(jié)構(gòu)體或者其嵌套的某個結(jié)構(gòu)體成員的最大成員大小的整數(shù)倍.不?的要補?。
根據(jù)對齊原則沙咏,我們具體來分析struct01 辨图、struct02結(jié)構(gòu)體:
結(jié)構(gòu)體MyStruct1 內(nèi)存大小計算:
- 變量
a
:占8
個字節(jié),從0
開始肢藐,min(0,8)
,即放在下0-7存儲
- 變量
b
:占1
個字節(jié)故河,從8
開始,min(8,1)
,8%1==0
吆豹,即放在8存儲
- 變量
c
:占8
個字節(jié)鱼的,從9
開始,min(9,8)
,9%8!=0
,往后移直到min(16,8)
,所以從變量c放在16-23存儲
- 變量
d
:占4
字節(jié)瞻讽,從24
開始鸳吸,min(24,4),24%4==0
,即d放在24-27存儲
- 變量
e
:占2
字節(jié),從28
開始速勇,min(28,2)
,28%2==0
,即e放在28-29存儲
因此struct01需要的內(nèi)存大小是30字節(jié)
[0-29],而LGHStruct01中最大變量的字節(jié)數(shù)為8晌砾,所以 LGHStruct01 實際的內(nèi)存大小必須是 8 的整數(shù)倍,30向上取整到32烦磁,主要是因為32是8的整數(shù)倍养匈,所以 sizeof(LGHStruct01) = 32
示意圖如下:
結(jié)構(gòu)體MyStruct02 內(nèi)存大小計算:
- 變量
a
:占8
個字節(jié),從0
開始都伪,min(0,8)
,即放在下0-7存儲
- 變量
b
:占8
個字節(jié)呕乎,從8
開始,min(8,8)
,8%8==0
陨晶,即放在8-15存儲
- 變量
c
:占4
個字節(jié)猬仁,從16
開始,min(16,4)
,16%4==0
,所以變量c放在16-19存儲
- 變量
d
:占2
字節(jié)先誉,從20
開始湿刽,min(20,2),20%2==0
,即d放在20-21存儲
- 變量
e
:占1
字節(jié),從22
開始褐耳,min(22,1)
,22%1==0
,即e放在22存儲
因此struct01需要的內(nèi)存大小是23字節(jié)
[0-22],而LGHStruct02中最大變量的字節(jié)數(shù)為8诈闺,所以 LGHStruct02 實際的內(nèi)存大小必須是 8 的整數(shù)倍,23向上取整到24铃芦,主要是因為24是8的整數(shù)倍雅镊,所以 sizeof(LGHStruct02) = 24
示意圖如下:
結(jié)構(gòu)體嵌套
struct LGHStruct03 {
int a;
char b;
short c;
float d;
struct LGHStruct01 str01;
}struct03;
NSLog(@"%lu",sizeof(struct03));
// 打印結(jié)果是48
LGHStruct03里面嵌套了LGHStruct01襟雷,
- 變量
a
:占4
個字節(jié),從0
開始仁烹,min(0,4)
,即放在下0-4存儲
- 變量
1
:占1
個字節(jié)耸弄,從4
開始,min(4,1)
,4%1==0
晃危,即放在4存儲
- 變量
c
:占2
個字節(jié)叙赚,從5
開始,min(5,2)
,5%2!=0
,向后移到min(6,2)
,6%2==0
, 變量c放在6-7存儲
- 變量
d
:占4
字節(jié)僚饭,從8
開始震叮,min(8,4),8%4==0
,即d放在8-11存儲
- 變量
str01
:占32
字節(jié),但str01里面最大變量所占的字節(jié)數(shù)是8
鳍鸵,從12
開始苇瓣,min(12,8)
,12%8!=0
,向后移到16
,16%8==0
,即e
放在16-47存儲
所以LGHStruct03所需的內(nèi)存是48偿乖,對齊字節(jié)數(shù)應(yīng)為當(dāng)前結(jié)構(gòu)體或者嵌套的某個結(jié)構(gòu)體成員的最大成員大小击罪,是LGHStruct01里面的long類型為8
,所以對齊數(shù)為8
贪薪,而48剛好是8的倍數(shù)媳禁,所以sizeof(struct03) = 48
.
如下圖所示:
內(nèi)存優(yōu)化(屬性重排)
我們知道蘋果它對OC對象的屬性的存儲是進(jìn)行了重排的。下面我們定義一個LGHPerson類画切,來看看屬性在內(nèi)存中的存儲竣稽。
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) long height;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) float hair;
@property (nonatomic, assign) short ID;
@property (nonatomic) char c1;
@property (nonatomic) char c2;
@property (nonatomic) char c3;
@property (nonatomic) char c4;
@property (nonatomic) char c5;
@end
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
person.name = @"Cooci";
person.nickName = @"KC";
person.age = 18;
person.height = 170;
person.hair = 100;
person.ID = 10;
person.c1 = 'a';
person.c2 = 'b';
person.c3 = 'c';
person.c4 = 'd';
person.c5 = 'e';
NSLog(@"%lu - %@ - %lu",sizeof(float),person,malloc_size((__bridge const void *)person));
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
我們在NSLog這里打個斷點,接下來看看每個屬性具體的存放位置:
**總結(jié): **
蘋果對OC對象的存儲進(jìn)行重排霍弹,根據(jù)屬性所占的字節(jié)數(shù)的大小毫别,從小到大排序,先存儲所占字節(jié)數(shù)較少的屬性典格,再存儲所占字節(jié)數(shù)較少的屬性岛宦,從而減少padding(內(nèi)存占位符),達(dá)到內(nèi)存優(yōu)化的效果.