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阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容