iOS對象的內(nèi)存分配討論-內(nèi)存對齊

前言:

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

參考:蘋果系統(tǒng)中各基本數(shù)據(jù)類型所需內(nèi)存

基本類型數(shù)據(jù)占內(nèi)存大小.png

一、內(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/4gxlldb的打印,第一個x就是memory read內(nèi)存讀取并打印的作用,/后面跟的4表示打印4個內(nèi)存單元,g表示8字節(jié)打印,而第二個x表示按6進(jìn)制格式顯示,可對應(yīng)這張參考圖看一下:

lldb指令X/4gx 打印相關(guān).png

2.打印結(jié)果展示:

上圖如下:


字節(jié)對齊打印驗證2.png

解讀一下這個現(xiàn)象:
①用x/8gx打印8個內(nèi)存段,調(diào)換a,b,c,d順序,打印size不一樣,存儲的字節(jié)順序也不一樣;struct1打印是24字節(jié),struct2打印是16字節(jié),符合總size大小的推測,紅圈部分structa8+8+8 =24,綠圈部分structb8+8=16;
②打印相關(guān)字節(jié)的值,能和a、b、c、d的值對應(yīng)上,如箭頭所示;
③a和c的值無法直接通過p打印出來,需借助e - f 指令,這里e -fexpression -format的簡寫,后面的ffloat浮點類型打印,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>

驗證結(jié)構(gòu)體嵌套.png

這幾個值內(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é)果說明:
簡單對象的內(nèi)存對齊.png

補(bǔ)充,對象的內(nèi)存打印的第一個8字節(jié)0x021d800100004889,是isa的內(nèi)存地址,在上一篇文章alloc的開辟內(nèi)存探討中已解釋過,這里不在贅述;后面的0x4054000000000000AHeight屬性值,圖[簡單對象的內(nèi)存對齊.png]中紅色標(biāo)注已正確打印,其他的也都打印正確;

2、打印下稍復(fù)雜對象:

這里的對象C包含一個屬性ff,ff是YPersona類的,那么對象C的內(nèi)存是否會是32以上呢?打印結(jié)果如下:

稍復(fù)雜對象的打印.png
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)系

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容