iOS-底層原理-內存對齊

1.iOS中獲取內存大小的三種方式

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

2.各自的含義是什么

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

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

2.3 malloc_size
這個函數是獲取系統實際分配的內存大小

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

@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.打印輸出三種獲取內存大小的方式

#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三種獲取內存大小的輸出,如下圖所示
1.sizeof 輸出長度是 8
2.class_getInstanceSize 輸出長度是 40
3.malloc_size 輸出長度是 48
4.iOS系統是16字節對齊,所以,真實需要的40個字節,16字節對齊、16的倍數,開辟48字節

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

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

malloc_size 系統開辟的內存空間大小,16字節對齊方式,所以對象真實內存大小是40,系統開辟16的整數倍是48

iOS查看內存大小的三種方式.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

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

isa 指針 8字節 [0 - 7]
name 8字節 [8 - 15]
nickName 8字節 [16 - 23]
age 4字節 [24 - 27]
weight 4字節 [28 - 31]
height 8字節 [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

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

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

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

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

截圖


對象內部真實存儲的值.jpeg

輸出

2022-05-03 13:26:52.492652+0800 002-系統內存開辟[2692:188124] person == <LGPerson: 0x600001ab87b0>
2022-05-03 13:26:52.492853+0800 002-系統內存開辟[2692:188124] sizeof == 8
2022-05-03 13:26:52.492911+0800 002-系統內存開辟[2692:188124] class_getInstanceSize == 48
2022-05-03 13:26:52.492966+0800 002-系統內存開辟[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.通過結構體來驗證一下內存對齊的基本原則、基本原理

1.數據類型在32位和64位情況下占用的字節數

1. 可見只有long 和 unsigned long在32位下是4個字節,64位下是8個字節,其他的都是一致的

數據類型字節長度.jpeg

2.兩個結構體數據類型和數量一致,只是數據類型的順序不一樣,結構體占用內存的長度不一致,如下圖所示

兩個結構體如下

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

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

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

結構體占用內存長度.jpeg

3.內存對齊原則

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

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

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

[原則二]
數據成員為結構體:如果一個結構體里有某些結構體成員,則該結構體成員要從其內部最大元素大小的整數倍地址開始存儲,比如struct a里有struct b, b里面有char, int, double, short,那么b應該從8的整數倍地址開始存儲,即最大成員為double 8字節。如果只有char, int, short,那么b從4的整數倍地址開始存儲,即最大成員為int 4字節

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

[原則三]
結構體內存的總大小,必須是結構體中最大成員內存的整數倍,不足的需要補齊

根據內存對齊原則分析一下上面的結構體內存情況

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

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

結論:結構體內存大小與結構體成員的內存大小順序有關

4. 結構體嵌套

代碼如下

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

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

struct LGStruct3 {
    double a; // 8字節,[0 1 2 3 4 5 6 7] ---> 存儲位置
    char b;   // 1字節,[8]               ---> 存儲位置
    int c;    // 4字節,[12 13 14 15]     ---> 存儲位置
    short d;  // 2字節,[16 17]           ---> 存儲位置
    struct LGStruct2 e; // 結構體作為成員變量,LGStruct2內部最大成員的整數倍地址開始存儲,也就是double 8的整數
} struct3;              // LGStruct2整體長度是16個字節,也就是從 [24-39]
                        // LGStruct2 a,8字節,[24 25 26 27 28 29 30 31]
                        // LGStruct2 b,4字節,[32 33 34 35]
                        // LGStruct2 c,1字節,[36]
                        // LGStruct2 d,2字節,[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結構體內部成員的順序整體大小會發生變化,遵循內存對齊原則
結論:無論是否嵌套結構體的大小都與成員變量內存大小的順序有關

2022-05-04 10:00:11.900852+0800 002-系統內存開辟[1784:83817] sizeof struct1 == 24
2022-05-04 10:00:11.901047+0800 002-系統內存開辟[1784:83817] sizeof struct2 == 16
2022-05-04 10:00:11.901096+0800 002-系統內存開辟[1784:83817] sizeof struct3 == 32

Xcode截圖如下


結構體嵌套.jpeg

3.內存優化(屬性重排)

1.通過可以看出,結構體的大小結構體成員內存大小的順序有關系

2.如果是結構體中數據成員是根據內存從小到大的順序定義的,根據內存對齊規則來計算結構體內存大小,需要增加有較大的內存padding即內存占位符,才能滿足內存對齊規則,比較浪費內存

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

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

LGPerson類結構如下

@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

調用如下,創建對象,并對屬性進行賦值

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字節的屬性
po 0x00000001003a8028 ---> DaShen ---> name指針8字節
po 0x00000001003a8048 ---> KC ---> nickName指針8字節
po 0x00000000000000b9 ---> 185 ---> height long 8字節

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

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

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

(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內存優化,屬性重排,截圖說明,person這個實例對象的內存情況和對應的值存儲情況

iOS內存優化屬性重排.jpeg

總結
所以,這里可以總結下蘋果中的內存對齊思想:

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

4.到底是8字節對齊,還是16字節對齊?

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

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

3.iOS新版的alloc流程,2個兩個函數都是16字節對齊的

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

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.計算需要開辟的內存空間大小
    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.向系統申請開辟內存,并返回指針
        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.指針關聯cls,設置isa指針
        obj->initIsa(cls);
    }

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

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

4.instanceSize()的調用順序

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

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

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

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

instanceSize函數()

    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()函數

    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()函數

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

4.calloc()的調用順序

需要在malloc源碼里面查找

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

segregated_size_to_fit ()函數是計算16字節對齊的

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()函數具體實現

#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的倍數

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

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

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()函數,假設 x = 2
(x + 15) & ~(15)
0001 0001 ---> 2 + 15
1111 0000 ---> ~15
0001 0000 ---> 17 &(~15) == 16 ---> 這樣本身2個字節就開辟了16個字節

segregated_size_to_fit()函數,假設 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個字節就開辟了16個字節

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

申請8字節, 開辟16字節
申請24字節,開辟32字節
申請32字節,開辟32字節
申請40字節,開辟48字節
申請41字節,開辟48字節

代碼如下

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()系統實際上開辟的內存大小
        
        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字節對齊


calloc 16字節對齊.jpeg

5.總結

1.iOS新版本采用16字節對齊的方式,計算需要開辟多少內存空間的函數和開辟內存空間的函數都是16字節對齊,雙16字節對齊
2.結構體內存對齊三原則
3.iOS系統優化優化內存,進行了屬性、成員變量等重排

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容