前言:
第三篇文章,技術(shù)討論的第二篇文章,上一篇文章里:iOS怎么捏泥人-alloc初探,我初步討論了在剛開始捏泥人(創(chuàng)建對象)中,關(guān)鍵方法alloc的調(diào)用鏈路及部分解釋,其中提到了開辟內(nèi)存,并間接提到了為了提高cpu訪問效率,采取的一個非常重要的策略:內(nèi)存對齊,這篇文章就分析下iOS是怎么做內(nèi)存對齊的。
參考:蘋果系統(tǒng)中各基本數(shù)據(jù)類型所需內(nèi)存

一、內(nèi)存對齊的基本原則:
1.數(shù)據(jù)成員對齊規(guī)則:
結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第一個數(shù)據(jù)成員放在offset為0的地方,以后每個數(shù)據(jù)成員存儲的起始位置要從該成員大小或者成員的子成員大?。ㄖ灰摮蓡T有子成員,比如說是數(shù)組,結(jié)構(gòu)體等)的整數(shù)倍開始(比如int在 32 位機(jī)為4字節(jié),則要從4的整數(shù)倍地址開始存儲。
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ǔ)齊。
這是什么意思?怎么理解這三個原則呢,看一段結(jié)構(gòu)體代碼:
struct YStruct1 {
double a; // 8 [0 7]
char b; // 1 [8]
int c; // 4 (9 10 11 [12 13 14 15]
short d; // 2 [16 17] 打印總字節(jié):24
} struct1;
struct YStruct2 {
double a; // 8 [0 7]
int b; // 4 [8 9 10 11]
char c; // 1 [12]
short d; // 2 (13 [14 15] 打印總字節(jié):16
} struct2;
struct YStruct3 {
double a; // 8 [0 7]
int b; // 4 [8 9 10 11]
char c; // 1 [12]
short d; // 2 (13 [14 15]
struct YStruct1 str; //[16 .. 23] [24] (25 26 27)[28 .. 31][32 33] 打印總字節(jié):40
} struct3;
int main(int argc, const char * argv[]) {
@autoreleasepool {
// struct1.a = 10.0;
NSLog(@"-----%lu-----%lu-----%lu----",sizeof(struct1),sizeof(struct2),sizeof(struct3));
struct YStruct1 structa = {
10.1,//double
'c',//char
2,//int
5//short
};
struct YStruct2 structb = {
10.1,
2,
'c',
5
};
struct YStruct3 structc = {
10.1,
2,
'c',
5,
structa
};
struct YStruct2 structd = {
10.1,
3,
'c',
5
};
NSLog(@"---test");
}
return 0;
}
根據(jù)第一張圖”基本類型數(shù)據(jù)占內(nèi)存大小.png“,對Ystruct1結(jié)構(gòu)體內(nèi)的a,b,c,d字節(jié)作出標(biāo)記,我們解讀下代碼里的備注:
①.a是double類型需要8字節(jié),并且是第一個成員,根據(jù)原則第一個數(shù)據(jù)成員放在offset為0的地方,那么a從第0位排到第7位,記作[0 7];
②.b是char類型占1字節(jié),根據(jù)起始位置要從該成員大小...的整數(shù)倍開始存儲,第8位是1的倍數(shù),那么b可以放在第8位,記為[8];
③.c是int占4字節(jié),開始必須是4的倍數(shù),那么9、10、11字節(jié)都不符合必須空著,只能從12開始排到15結(jié)束;整體排到d是[16 17];
④.總體的話,struct1從0位到12位共18字節(jié),總字節(jié)需要補(bǔ)齊到最大字節(jié)8的倍數(shù),就是24字節(jié);同理,sturct2結(jié)構(gòu)的也如備注中所示,總字節(jié)為16
同理,YStrcut2的字節(jié)也如代碼中相應(yīng)結(jié)構(gòu)體備注所示
二、內(nèi)存對齊打印驗證:
1.打印指令:
x/4gx是lldb的打印,第一個x就是memory read內(nèi)存讀取并打印的作用,/后面跟的4表示打印4個內(nèi)存單元,g表示8字節(jié)打印,而第二個x表示按6進(jìn)制格式顯示,可對應(yīng)這張參考圖看一下:

2.打印結(jié)果展示:
上圖如下:

解讀一下這個現(xiàn)象:
①用x/8gx打印8個內(nèi)存段,調(diào)換a,b,c,d順序,打印size不一樣,存儲的字節(jié)順序也不一樣;struct1打印是24字節(jié),struct2打印是16字節(jié),符合總size大小的推測,紅圈部分structa為8+8+8 =24,綠圈部分structb為8+8=16;
②打印相關(guān)字節(jié)的值,能和a、b、c、d的值對應(yīng)上,如箭頭所示;
③a和c的值無法直接通過p打印出來,需借助e - f 指令,這里e -f是expression -format的簡寫,后面的f是float浮點類型打印,char類型用c表示,具體可用lldb輸入help查詢;
④p/x輸出一個數(shù)據(jù)結(jié)構(gòu)的首地址
補(bǔ)充一下結(jié)構(gòu)體的嵌套:
那么以上說明struct的打印是符合內(nèi)存對齊的3個原則的,這里再驗證下,結(jié)構(gòu)體里面包含結(jié)構(gòu)體是什么樣的,代碼中的structc就是這種情況,看下打?。?br>

這幾個值內(nèi)存字節(jié)對應(yīng)的值前面已經(jīng)打印過,這里是對的上的,黃色圈部分是
structc的內(nèi)存字節(jié),占40字節(jié),其余不再說明,這里stuct中字節(jié)對齊的原則已經(jīng)驗證結(jié)束,那么常規(guī)的對象是否也有這個規(guī)律呢?下面來說明;
三、驗證常規(guī)對象的內(nèi)存對齊:
show me code,上代碼:
@interface YPersona : NSObject
@property (nonatomic, assign) double height;
@property (nonatomic, assign) char name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) short point;
@end
@interface YPersonb : NSObject
@property (nonatomic, assign) double height;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) char name;
@property (nonatomic, assign) short point;
@end
@interface YPersonc : NSObject
@property (nonatomic, assign) double height;
@property (nonatomic, assign) char name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) short point;
@property (nonatomic, strong) YPersona *ff;
@end
@implementation YPersona
@end
@implementation YPersonb
@end
@implementation YPersonc
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// struct1.a = 10.0;
YPersona *A = [YPersona new];
A.height = 80.0;
A.age = 18;
A.name = 'A';
A.point = 1;
YPersonb *B = [YPersonb new];
B.height = 80.0;
B.age = 18;
B.name = 'A';
B.point = 1;
YPersonc *C = [YPersonc new];
C.height = 80.0;
C.age = 18;
C.name = 'A';
C.point = 1;
C.ff = A;
NSLog(@"--A---%lu--B---%lu--C---%lu----",sizeof(A),sizeof(B),sizeof(C));
NSLog(@"--A實際內(nèi)存---%zu--B實際內(nèi)存---%zu--C實際內(nèi)存---%zu----",malloc_size((__bridge const void *)A),malloc_size((__bridge const void *)B),malloc_size((__bridge const void *)C));
NSLog(@"---test");
}
return 0;
}
1、打印結(jié)果說明:

補(bǔ)充,對象的內(nèi)存打印的第一個8字節(jié)0x021d800100004889,是isa的內(nèi)存地址,在上一篇文章alloc的開辟內(nèi)存探討中已解釋過,這里不在贅述;后面的0x4054000000000000是A的Height屬性值,圖[簡單對象的內(nèi)存對齊.png]中紅色標(biāo)注已正確打印,其他的也都打印正確;
2、打印下稍復(fù)雜對象:
這里的對象C包含一個屬性ff,ff是YPersona類的,那么對象C的內(nèi)存是否會是32以上呢?打印結(jié)果如下:

3、對象內(nèi)存對齊的總結(jié):
①、整體的屬性內(nèi)存訪問是的確如struct一樣8字節(jié)的對齊的;
②、對象的總內(nèi)存是16字節(jié)對齊的(A、B對象實際只有24字節(jié),但都加了8個空字節(jié)補(bǔ)足為32,即16的倍數(shù),而C對象正好為32不用補(bǔ)),源代碼的解讀后面再補(bǔ);
③、對象嵌套時,對象是有區(qū)別的,嵌套對象的內(nèi)存表示是8字節(jié)存的對象的內(nèi)存地址。
四、補(bǔ)充:
0x10104b200: 0x021d800100004889 0x0000001200010041這是什么?
1、0x10104b200這個一般表示堆棧地址,即對象內(nèi)容的存儲地址,一個堆棧地址可存16字節(jié);
2、0x021d800100004889 0x0000001200010041這個是冒號后面的、屬性的內(nèi)存對齊,按字節(jié)表示的,打印不同的字節(jié)可以查詢到對應(yīng)的值
3、對象里面包含對象(有聲明屬性)時,子對象在父對象的內(nèi)存字節(jié)排布中,用子對象的內(nèi)存地址表明。
4、冒號前面的值,堆棧地址在計算上有關(guān)聯(lián),可以內(nèi)存平移通過16進(jìn)制+10這種方式計算;冒號后面的地址沒有計算關(guān)系