iOS-底層原理-內(nèi)存對齊

1.iOS中獲取內(nèi)存大小的三種方式

1.獲取內(nèi)存大小的三種方式分別是:
1.1 sizeof
1.2 class_getInstanceSize
1.3 malloc_size
后兩者需要添加對應(yīng)的頭文件 #import <objc/runtime.h> #import <malloc/malloc.h>

2.各自的含義是什么

2.1 sizeof
sizeof 是一個操作符,不是函數(shù)
我們一般用sizeof計算內(nèi)存大小時,傳入的對象主要是數(shù)據(jù)類型,這個是在編譯階段就會確定大小而不是運行時
sizeof最終得到的結(jié)果是該數(shù)據(jù)類型占用空間的大小

2.2 class_getInstanceSize
class_getInstanceSizeruntime提供的api,用于獲取類的實例對象所占用的內(nèi)存大小,并返回具體的字節(jié)數(shù),其本質(zhì)就是獲取實例對象中成員變量的內(nèi)存大小,也就是對象真正需要的內(nèi)存大小, 和里面的屬性和成員變量相關(guān)

2.3 malloc_size
這個函數(shù)是獲取系統(tǒng)實際分配的內(nèi)存大小

3.下面通過自定義的類LGPerson來測試一下各自的內(nèi)存輸出大小以及怎么來理解

@interface LGPerson : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;
@end

4.打印輸出三種獲取內(nèi)存大小的方式

#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "LGPerson.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        LGPerson *person = [LGPerson alloc];
        person.name      = @"DaShen";
        person.nickName  = @"KC";

        NSLog(@"person == %@",person);
        NSLog(@"sizeof == %lu", sizeof(person));
        NSLog(@"class_getInstanceSize == %lu", class_getInstanceSize([LGPerson class]));
        NSLog(@"malloc_size == %lu", malloc_size((__bridge const void *)(person)));

        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

4.1三種獲取內(nèi)存大小的輸出,如下圖所示
1.sizeof 輸出長度是 8
2.class_getInstanceSize 輸出長度是 40
3.malloc_size 輸出長度是 48
4.iOS系統(tǒng)是16字節(jié)對齊,所以,真實需要的40個字節(jié),16字節(jié)對齊、16的倍數(shù),開辟48字節(jié)

解釋如下
sizeof(person) 因為person是指針,所以,占用的內(nèi)存大小是8字節(jié)

class_getInstanceSize([LGPerson class]) 對象真正需要的內(nèi)存大小
isa 指針 8字節(jié) [0 - 7]
name 8字節(jié) [8 - 15]
nickName 8字節(jié) [16 - 23]
age 4字節(jié) [24 - 27]
height 8字節(jié) [32 - 39]
說明,順序不一定是這樣的,但是原理是這樣的

malloc_size 系統(tǒng)開辟的內(nèi)存空間大小,16字節(jié)對齊方式,所以對象真實內(nèi)存大小是40,系統(tǒng)開辟16的整數(shù)倍是48

iOS查看內(nèi)存大小的三種方式.jpeg

4.2 LGPerson類新增一個int weight的屬性

@interface LGPerson : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int weight;
@property (nonatomic, assign) long height;

@end

可以看出輸出結(jié)果完全一致,
1.sizeof 輸出長度是 8
2.class_getInstanceSize 輸出長度是 40
3.malloc_size 輸出長度是 48
4.iOS系統(tǒng)是16字節(jié)對齊,所以,真實需要的40個字節(jié),16字節(jié)對齊、16的倍數(shù),開辟48字節(jié)

isa 指針 8字節(jié) [0 - 7]
name 8字節(jié) [8 - 15]
nickName 8字節(jié) [16 - 23]
age 4字節(jié) [24 - 27]
weight 4字節(jié) [28 - 31]
height 8字節(jié) [32 - 39]
說明,順序不一定是這樣的,但是原理是這樣的

新增int weight屬性.jpeg

4.3 LGPerson類再次新增一個NSString * home的成員變量

@interface LGPerson : NSObject
{
    NSString * _home;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int weight;
@property (nonatomic, assign) long height;

@end

輸出結(jié)果如下
1.sizeof 輸出長度是 8
2.class_getInstanceSize 輸出長度是 48
3.malloc_size 輸出長度是 48
4.iOS系統(tǒng)是16字節(jié)對齊,所以,需要的剛好是48字節(jié)和系統(tǒng)開辟剛好一致

isa 指針 8字節(jié) [0 - 7]
name 8字節(jié) [8 - 15]
nickName 8字節(jié) [16 - 23]
age 4字節(jié) [24 - 27]
weight 4字節(jié) [28 - 31]
height 8字節(jié) [32 - 39]
home 8字節(jié) [40 - 47]
說明,順序不一定是這樣的,但是原理是這樣的

再次增加NSString 類型成員變量.jpeg

4.4來看一下這些屬性和成員變量的真實存儲情況
1.上面得出來了,int類型的age和int類型的weight占用一個8個字節(jié)的存儲空間
2.下面用 x/6gx person 打印6段8個字節(jié)的存儲空間,內(nèi)存地址所存儲的具體內(nèi)容見圖片注釋文字

截圖


對象內(nèi)部真實存儲的值.jpeg

輸出

2022-05-03 13:26:52.492652+0800 002-系統(tǒng)內(nèi)存開辟[2692:188124] person == <LGPerson: 0x600001ab87b0>
2022-05-03 13:26:52.492853+0800 002-系統(tǒng)內(nèi)存開辟[2692:188124] sizeof == 8
2022-05-03 13:26:52.492911+0800 002-系統(tǒng)內(nèi)存開辟[2692:188124] class_getInstanceSize == 48
2022-05-03 13:26:52.492966+0800 002-系統(tǒng)內(nèi)存開辟[2692:188124] malloc_size == 48
(lldb) x/6gx person
0x600001ab87b0: 0x01000001027115e9 0x000000010270c068
0x600001ab87c0: 0x0000009600000012 0x000000010270c028
0x600001ab87d0: 0x000000010270c048 0x00000000000000b9
(lldb) po 0x600001ab87b0
<LGPerson: 0x600001ab87b0>

(lldb) po 0x01000001027115e9
72057598373860841

(lldb) po 0x000000010270c068
北京

(lldb) po 0x000000010270c028
DaShen

(lldb) po 0x000000010270c048
KC

(lldb) po 0x00000000000000b9
185

(lldb) po 0x0000009600000012
644245094418

(lldb) po 0x96
150

(lldb) po 0x12 
18

(lldb) 

2.通過結(jié)構(gòu)體來驗證一下內(nèi)存對齊的基本原則、基本原理

1.數(shù)據(jù)類型在32位和64位情況下占用的字節(jié)數(shù)

1. 可見只有l(wèi)ong 和 unsigned long在32位下是4個字節(jié),64位下是8個字節(jié),其他的都是一致的

數(shù)據(jù)類型字節(jié)長度.jpeg

2.兩個結(jié)構(gòu)體數(shù)據(jù)類型和數(shù)量一致,只是數(shù)據(jù)類型的順序不一樣,結(jié)構(gòu)體占用內(nèi)存的長度不一致,如下圖所示

兩個結(jié)構(gòu)體如下

struct LGStruct1 {
    double a;
    char b;
    int c;
    short d;
} struct1;

struct LGStruct2 {
    double a;
    int b;
    char c;
    short d;
} struct2;

1. struct1 數(shù)據(jù)類型:double, char, int, short
2. struct2 數(shù)據(jù)類型:double, int , char, short
3.可見兩個結(jié)構(gòu)體只是int和char數(shù)據(jù)類型交換了順序,得出來的結(jié)構(gòu)體內(nèi)存長度是不一樣的,唯一的區(qū)別只是在于定義變量的順序不一致,那為什么它們占用的內(nèi)存大小不相等呢?其實這就是iOS中的內(nèi)存字節(jié)對齊現(xiàn)象
sizeof struct1 == 24
sizeof struct2 == 16

結(jié)構(gòu)體占用內(nèi)存長度.jpeg

3.內(nèi)存對齊原則

每個特定平臺上的編譯器都有字節(jié)的默認“對齊系數(shù)”(也叫對齊模數(shù))。程序員可以通過預(yù)編譯命令#pragma pack(),n = 1, 2, 4, 8, 18來改變這一系數(shù),其中的n就是你要指定的“對齊系數(shù)”。在iOS中,Xcode默認為``#pragma pack(8)```,即8字節(jié)對齊

[原則一]
結(jié)構(gòu)體(struct)或者聯(lián)合體(union)的數(shù)據(jù)成員,第一個數(shù)據(jù)成員放在offset為0的地方,以后每個數(shù)據(jù)成員存儲的起始位置要從該成員大小的整數(shù)倍開始(比如 int 4字節(jié),那么存儲位置可以是0-4-8-12-16-20 依次類推)

數(shù)據(jù)成員的對齊規(guī)則可以理解為min(m, n) 的公式, 其中 m表示當前成員的開始位置, n表示當前成員所需要的位數(shù)。如果滿足條件 m 整除 n (即 m % n == 0), n 從 m 位置開始存儲, 反之繼續(xù)檢查m+1 能否整除 n, 直到可以整除, 從而就確定了當前成員的開始位置

[原則二]
數(shù)據(jù)成員為結(jié)構(gòu)體:如果一個結(jié)構(gòu)體里有某些結(jié)構(gòu)體成員,則該結(jié)構(gòu)體成員要從其內(nèi)部最大元素大小的整數(shù)倍地址開始存儲,比如struct a里有struct b, b里面有char, int, double, short,那么b應(yīng)該從8的整數(shù)倍地址開始存儲,即最大成員為double 8字節(jié)。如果只有char, int, short,那么b從4的整數(shù)倍地址開始存儲,即最大成員為int 4字節(jié)。

且這里的8和4視為b的自身長度作為外部結(jié)構(gòu)體的成員變量的內(nèi)存大小,即b這個結(jié)構(gòu)體可以視作8或者4字節(jié)這樣的結(jié)構(gòu)體成員,參與結(jié)構(gòu)體總大小必須是內(nèi)部最大成員的整數(shù)倍的計算

[原則三]
結(jié)構(gòu)體內(nèi)存的總大小,必須是結(jié)構(gòu)體中最大成員內(nèi)存的整數(shù)倍,不足的需要補齊

根據(jù)內(nèi)存對齊原則分析一下上面的結(jié)構(gòu)體內(nèi)存情況

struct LGStruct1 {
    double a; // 8字節(jié),[0 1 2 3 4 5 6 7] ---> 存儲位置
    char b;   // 1字節(jié),[8]               ---> 存儲位置
    int c;    // 4字節(jié),[12 13 14 15]     ---> 存儲位置
    short d;  // 2字節(jié),[16 17]           ---> 存儲位置
} struct1;    // 0-17,總共18個字節(jié),結(jié)構(gòu)體整體內(nèi)存大小必須是最大成員的整數(shù)倍,
              // 最大成員是double 為8字節(jié),也就是要從18個字節(jié)補齊到24個字節(jié)

struct LGStruct2 {
    double a; // 8字節(jié),[0 1 2 3 4 5 6 7] ---> 存儲位置
    int b;    // 4字節(jié),[8 9 10 11]       ---> 存儲位置
    char c;   // 1字節(jié),[12]              ---> 存儲位置
    short d;  // 2字節(jié),[14 15]           ---> 存儲位置
} struct2;    // 0-15,總共16個字節(jié),結(jié)構(gòu)體整體內(nèi)存大小必須是最大成員的整數(shù)倍,
              // 最大成員是double 為8字節(jié),恰好是8的兩倍不需要補齊

結(jié)論:結(jié)構(gòu)體內(nèi)存大小與結(jié)構(gòu)體成員的內(nèi)存大小順序有關(guān)

4. 結(jié)構(gòu)體嵌套

代碼如下

struct LGStruct1 {
    double a; // 8字節(jié),[0 1 2 3 4 5 6 7] ---> 存儲位置
    char b;   // 1字節(jié),[8]               ---> 存儲位置
    int c;    // 4字節(jié),[12 13 14 15]     ---> 存儲位置
    short d;  // 2字節(jié),[16 17]           ---> 存儲位置
} struct1;
              // 0-17,總共18個字節(jié),結(jié)構(gòu)體整體內(nèi)存大小必須是最大成員的整數(shù)倍,
              // 最大成員是double 為8字節(jié),也就是要從18個字節(jié)補齊到24個字節(jié)

struct LGStruct2 {
    double a; // 8字節(jié),[0 1 2 3 4 5 6 7] ---> 存儲位置
    int b;    // 4字節(jié),[8 9 10 11]       ---> 存儲位置
    char c;   // 1字節(jié),[12]              ---> 存儲位置
    short d;  // 2字節(jié),[14 15]           ---> 存儲位置
} struct2;
              // 0-15,總共16個字節(jié),結(jié)構(gòu)體整體內(nèi)存大小必須是最大成員的整數(shù)倍,
              // 最大成員是double 為8字節(jié),恰好是8的兩倍不需要補齊

struct LGStruct3 {
    double a; // 8字節(jié),[0 1 2 3 4 5 6 7] ---> 存儲位置
    char b;   // 1字節(jié),[8]               ---> 存儲位置
    int c;    // 4字節(jié),[12 13 14 15]     ---> 存儲位置
    short d;  // 2字節(jié),[16 17]           ---> 存儲位置
    struct LGStruct2 e; // 結(jié)構(gòu)體作為成員變量,LGStruct2內(nèi)部最大成員的整數(shù)倍地址開始存儲,也就是double 8的整數(shù)
} struct3;              // LGStruct2整體長度是16個字節(jié),也就是從 [24-39]
                        // LGStruct2 a,8字節(jié),[24 25 26 27 28 29 30 31]
                        // LGStruct2 b,4字節(jié),[32 33 34 35]
                        // LGStruct2 c,1字節(jié),[36]
                        // LGStruct2 d,2字節(jié),[38 39]


int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        NSLog(@"sizeof struct1 == %lu",sizeof(struct1));
        NSLog(@"sizeof struct2 == %lu", sizeof(struct2));
        NSLog(@"sizeof struct3 == %lu", sizeof(struct3));
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

輸出如下
1.這里的24 + 16 = 40僅僅是巧合,變換LGStruct3結(jié)構(gòu)體內(nèi)部成員的順序整體大小會發(fā)生變化,遵循內(nèi)存對齊原則
結(jié)論:無論是否嵌套結(jié)構(gòu)體的大小都與成員變量內(nèi)存大小的順序有關(guān)

2022-05-04 10:00:11.900852+0800 002-系統(tǒng)內(nèi)存開辟[1784:83817] sizeof struct1 == 24
2022-05-04 10:00:11.901047+0800 002-系統(tǒng)內(nèi)存開辟[1784:83817] sizeof struct2 == 16
2022-05-04 10:00:11.901096+0800 002-系統(tǒng)內(nèi)存開辟[1784:83817] sizeof struct3 == 32

Xcode截圖如下


結(jié)構(gòu)體嵌套.jpeg

3.內(nèi)存優(yōu)化(屬性重排)

1.通過可以看出,結(jié)構(gòu)體的大小結(jié)構(gòu)體成員內(nèi)存大小的順序有關(guān)系

2.如果是結(jié)構(gòu)體中數(shù)據(jù)成員是根據(jù)內(nèi)存從小到大的順序定義的,根據(jù)內(nèi)存對齊規(guī)則來計算結(jié)構(gòu)體內(nèi)存大小,需要增加有較大的內(nèi)存padding即內(nèi)存占位符,才能滿足內(nèi)存對齊規(guī)則,比較浪費內(nèi)存

3.如果是結(jié)構(gòu)體中數(shù)據(jù)成員是根據(jù)內(nèi)存從大到小的順序定義的,根據(jù)內(nèi)存對齊規(guī)則來計算結(jié)構(gòu)體內(nèi)存大小,我們只需要補齊少量內(nèi)存padding即可滿足堆存對齊規(guī)則,這種方式就是蘋果中采用的,利用空間換時間,將類中的屬性進行重排,來達到優(yōu)化內(nèi)存的目的

4.下面通過LGPerson自定義類里面屬性的調(diào)整來看看具體的屬性重排,其實與最開始看到的是一樣的,這里動態(tài)調(diào)整屬性會更直觀

LGPerson類結(jié)構(gòu)如下

@interface LGPerson : NSObject
{
    char c3;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;
@property (nonatomic) char c1;
@property (nonatomic) char c2;
- (void)setCharValue:(char)c;
@end

@implementation LGPerson
- (void)setCharValue:(char)c {
    c3 = c;
}
@end

調(diào)用如下,創(chuàng)建對象,并對屬性進行賦值

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        LGPerson *person = [LGPerson alloc];
        person.name      = @"DaShen";
        person.nickName  = @"KC";
        person.age = 18;
        person.height = 185;
        person.c1 = 'a';
        person.c2 = 'b';
        [person setCharValue:'c'];
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

讀取屬性值如下

isa
0x01000001003ad611 ---> isa 存儲值

單獨占用8字節(jié)的屬性
po 0x00000001003a8028 ---> DaShen ---> name指針8字節(jié)
po 0x00000001003a8048 ---> KC ---> nickName指針8字節(jié)
po 0x00000000000000b9 ---> 185 ---> height long 8字節(jié)

內(nèi)存優(yōu)化,屬性重排
po 0x0000001200626163 ---> 77315858787讀取這個值時出現(xiàn)亂碼,這里無法找到剩余屬性和成員變量值的原因是蘋果針對age、c1、c2、c3屬性和成員變量的內(nèi)存進行了重排,因為age int類型占用4個字節(jié),c1、c2、c3都是char類型,每個占用一個字節(jié),所以形成4+1+1+1的方式,按照8字節(jié)對齊,不足補齊的原則存儲在一看內(nèi)存中

age、c1、c2、c3的讀取
a的ASCII碼值是97
b的ASCII碼值是98
c的ASCII碼值是99

po 0x00000012 ---> 18 ---> age int 4字節(jié)
po 0x61 ---> 97 ---> c1 char 1字節(jié)
po 0x62 ---> 98 ---> c2 char 1字節(jié)
po 0x63 ---> 99 ---> c3 char 1字節(jié)

(lldb) x/6gx person
0x6000029507b0: 0x01000001003ad611 0x0000001200626163
0x6000029507c0: 0x00000001003a8028 0x00000001003a8048
0x6000029507d0: 0x00000000000000b9 0x0000000000000000
(lldb) po 0x00000001003a8028
DaShen

(lldb) po 0x00000001003a8048
KC

(lldb) po 0x00000000000000b9
185

(lldb) po 0x0000001200626163
77315858787

(lldb) po 0x12
18

(lldb) po 0x61
97

(lldb) po 0x62
98

(lldb) po 0x63
99

(lldb) 

iOS內(nèi)存優(yōu)化,屬性重排,截圖說明,person這個實例對象的內(nèi)存情況和對應(yīng)的值存儲情況

iOS內(nèi)存優(yōu)化屬性重排.jpeg

總結(jié)
所以,這里可以總結(jié)下蘋果中的內(nèi)存對齊思想:

大部分的內(nèi)存都是通過固定的內(nèi)存塊進行讀取,
盡管我們在內(nèi)存中采用了內(nèi)存對齊的方式,但并不是所有的內(nèi)存都可以進行浪費的,蘋果會自動對屬性進行重排,以此來優(yōu)化內(nèi)存

4.到底是8字節(jié)對齊,還是16字節(jié)對齊?

1.對于一個對象來說,8個字節(jié)就能滿足需求了,但是,會出現(xiàn)一個對象緊挨著另一個對象的情況,中間毫無間隙,apple系統(tǒng)為了防止一切的容錯,采用的是16字節(jié)對齊的內(nèi)存,主要是因為采用8字節(jié)對齊時,兩個對象的內(nèi)存會緊挨著,顯得比較緊湊,而16字節(jié)比較寬松,利于蘋果以后的擴展。

2.如果不全是16的整數(shù)倍的話,中間會有8個字節(jié)的間隔,比如一個對象需要24個字節(jié)16字節(jié)內(nèi)存對齊的話,會給該對象開辟32個字節(jié)的內(nèi)存空間,后面的8個字節(jié)沒有任何數(shù)據(jù),預(yù)留給這個對象的,防止可能的出錯

3.iOS新版的alloc流程,2個兩個函數(shù)都是16字節(jié)對齊的

1.instanceSize() ---> 計算需要開辟的內(nèi)存空間大小 ---> 16字節(jié)對齊
2.calloc() ---> 開辟內(nèi)存,內(nèi)部最終內(nèi)部調(diào)用的是16字節(jié)對齊 ---> 16字節(jié)對齊

static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ASSERT(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;

    // 1.計算需要開辟的內(nèi)存空間大小
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        // 2.向系統(tǒng)申請開辟內(nèi)存,并返回指針
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        // 3.指針關(guān)聯(lián)cls,設(shè)置isa指針
        obj->initIsa(cls);
    }

    if (fastpath(!hasCxxCtor)) {
        return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}

4.instanceSize()的調(diào)用順序

instanceSize() ---> fastInstanceSize --->align16()

#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))

__builtin_expect這個指令是 gcc引入的,作用是允許程序員將最有可能執(zhí)行的分支告訴編譯器。
這個指令的寫法為:__builtin_expect(EXP, N)。意思是:EXP==N 的概率很大

所以fastpath的含義是,為1的概率大,slowpath 的含義是為 0的概率大
fastpath(cache.hasFastInstanceSize(extraBytes))是大概率執(zhí)行的,也就是16字節(jié)對齊

instanceSize函數(shù)()

    inline size_t instanceSize(size_t extraBytes) const {
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }

        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

fastInstanceSize()函數(shù)

    size_t fastInstanceSize(size_t extra) const
    {
        ASSERT(hasFastInstanceSize(extra));

        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        } else {
            size_t size = _flags & FAST_CACHE_ALLOC_MASK;
            // remove the FAST_CACHE_ALLOC_DELTA16 that was added
            // by setFastInstanceSize
            return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
        }
    }

align16()函數(shù)

static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}

4.calloc()的調(diào)用順序

需要在malloc源碼里面查找

calloc() ---> malloc_zone_calloc() ---> default_zone_calloc() ---> runtime_default_zone() ---> nano_calloc() ---> _nano_malloc_check_clear()

segregated_size_to_fit ()函數(shù)是計算16字節(jié)對齊的

static void *
_nano_malloc_check_clear(nanozone_t *nanozone, size_t size, boolean_t cleared_requested)
{
    MALLOC_TRACE(TRACE_nano_malloc, (uintptr_t)nanozone, size, cleared_requested, 0);

    void *ptr;
    size_t slot_key;
    size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key); // Note slot_key is set here
    mag_index_t mag_index = nano_mag_index(nanozone);

    nano_meta_admin_t pMeta = &(nanozone->meta_data[mag_index][slot_key]);

    ptr = OSAtomicDequeue(&(pMeta->slot_LIFO), offsetof(struct chained_block_s, next));
    if (ptr) {
        unsigned debug_flags = nanozone->debug_flags;
#if NANO_FREE_DEQUEUE_DILIGENCE
        size_t gotSize;
        nano_blk_addr_t p; // the compiler holds this in a register

        p.addr = (uint64_t)ptr; // Begin the dissection of ptr
        if (NANOZONE_SIGNATURE != p.fields.nano_signature) {
            malloc_zone_error(debug_flags, true,
                    "Invalid signature for pointer %p dequeued from free list\n",
                    ptr);
        }

        if (mag_index != p.fields.nano_mag_index) {
            malloc_zone_error(debug_flags, true,
                    "Mismatched magazine for pointer %p dequeued from free list\n",
                    ptr);
        }

        gotSize = _nano_vet_and_size_of_free(nanozone, ptr);
        if (0 == gotSize) {
            malloc_zone_error(debug_flags, true,
                    "Invalid pointer %p dequeued from free list\n", ptr);
        }
        if (gotSize != slot_bytes) {
            malloc_zone_error(debug_flags, true,
                    "Mismatched size for pointer %p dequeued from free list\n",
                    ptr);
        }

        if (!_nano_block_has_canary_value(nanozone, ptr)) {
            malloc_zone_error(debug_flags, true,
                    "Heap corruption detected, free list canary is damaged for %p\n"
                    "*** Incorrect guard value: %lu\n", ptr,
                    ((chained_block_t)ptr)->double_free_guard);
        }

#if defined(DEBUG)
        void *next = (void *)(((chained_block_t)ptr)->next);
        if (next) {
            p.addr = (uint64_t)next; // Begin the dissection of next
            if (NANOZONE_SIGNATURE != p.fields.nano_signature) {
                malloc_zone_error(debug_flags, true,
                        "Invalid next signature for pointer %p dequeued from free "
                        "list, next = %p\n", ptr, "next");
            }

            if (mag_index != p.fields.nano_mag_index) {
                malloc_zone_error(debug_flags, true,
                        "Mismatched next magazine for pointer %p dequeued from "
                        "free list, next = %p\n", ptr, next);
            }

            gotSize = _nano_vet_and_size_of_free(nanozone, next);
            if (0 == gotSize) {
                malloc_zone_error(debug_flags, true,
                        "Invalid next for pointer %p dequeued from free list, "
                        "next = %p\n", ptr, next);
            }
            if (gotSize != slot_bytes) {
                malloc_zone_error(debug_flags, true,
                        "Mismatched next size for pointer %p dequeued from free "
                        "list, next = %p\n", ptr, next);
            }
        }
#endif /* DEBUG */
#endif /* NANO_FREE_DEQUEUE_DILIGENCE */

        ((chained_block_t)ptr)->double_free_guard = 0;
        ((chained_block_t)ptr)->next = NULL; // clear out next pointer to protect free list
    } else {
        ptr = segregated_next_block(nanozone, pMeta, slot_bytes, mag_index);
    }

    if (cleared_requested && ptr) {
        memset(ptr, 0, slot_bytes); // TODO: Needs a memory barrier after memset to ensure zeroes land first?
    }
    return ptr;
}

segregated_size_to_fit()函數(shù)具體實現(xiàn)

#define SHIFT_NANO_QUANTUM      4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM)   // 16
static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
    size_t k, slot_bytes;

    if (0 == size) {
        size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
    }
    k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
    slot_bytes = k << SHIFT_NANO_QUANTUM;                           // multiply by power of two quanta size
    *pKey = k - 1;                                                  // Zero-based!

    return slot_bytes;
}

核心代碼解析
先左移4位,16以下全部抹零,再右移4位,剛好是16的倍數(shù)

k = (size + 15) >> 4
slot_bytes = k << 4

5.16字節(jié)對齊算法,兩種方式,結(jié)果是一樣的,下面二進制演示以下

align16()
segregated_size_to_fit()

static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}
k = (size + 15) >> 4
slot_bytes = k << 4

align16()函數(shù),假設(shè) x = 2
(x + 15) & ~(15)
0001 0001 ---> 2 + 15
1111 0000 ---> ~15
0001 0000 ---> 17 &(~15) == 16 ---> 這樣本身2個字節(jié)就開辟了16個字節(jié)

segregated_size_to_fit()函數(shù),假設(shè) size = 2
k = (size + 15) >> 4
slot_bytes = k << 4
0001 0001 ---> 2 + 15 == 17
0000 0001 ---> 2 + 15 == 17 >> 4 右移4位,16以下全部抹零,相當于 k / 16
0001 0000 ---> 17 << 4 左移4位,擴大16倍,相當于k * 16 ---> 這樣本身2個字節(jié)就開辟了16個字節(jié)

6.calloc()實際開辟內(nèi)存的大小,16字節(jié)對齊驗證

申請8字節(jié), 開辟16字節(jié)
申請24字節(jié),開辟32字節(jié)
申請32字節(jié),開辟32字節(jié)
申請40字節(jié),開辟48字節(jié)
申請41字節(jié),開辟48字節(jié)

代碼如下

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        void * p1 = calloc(1, 24);
        NSLog(@"sizeof p1 == %lu", sizeof(p1));
        NSLog(@"malloc_size p1 == %lu", malloc_size(p1)); //malloc_size()系統(tǒng)實際上開辟的內(nèi)存大小
        
        NSLog(@"\n\n");
        
        void * p2 = calloc(1, 32);
        NSLog(@"sizeof p2 == %lu", sizeof(p2));
        NSLog(@"malloc_size p2 == %lu", malloc_size(p2));
        
        NSLog(@"\n\n");
        
        void * p3 = calloc(1, 40);
        NSLog(@"sizeof p3 == %lu", sizeof(p3));
        NSLog(@"malloc_size p3 == %lu", malloc_size(p3));
        
        NSLog(@"\n\n");
        
        void * p4 = calloc(1, 41);
        NSLog(@"sizeof p4 == %lu", sizeof(p4));
        NSLog(@"malloc_size p4 == %lu", malloc_size(p4));
        
        NSLog(@"\n\n");
        
        void * p5 = calloc(1, 8);
        NSLog(@"sizeof p5 == %lu", sizeof(p5));
        NSLog(@"malloc_size p5 == %lu", malloc_size(p5));
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

Xcode截圖如下,calloc()16字節(jié)對齊


calloc 16字節(jié)對齊.jpeg

5.總結(jié)

1.iOS新版本采用16字節(jié)對齊的方式,計算需要開辟多少內(nèi)存空間的函數(shù)和開辟內(nèi)存空間的函數(shù)都是16字節(jié)對齊,雙16字節(jié)對齊
2.結(jié)構(gòu)體內(nèi)存對齊三原則
3.iOS系統(tǒng)優(yōu)化優(yōu)化內(nèi)存,進行了屬性、成員變量等重排

最后編輯于
?著作權(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)容