iOS 内存对齐探索

2020-09-08  本文已影响0人  远方竹叶

什么是内存对齐

内存对齐是一种在计算机内存中排列数据(表现为变量的地址)、访问数据(表现为CPU读取数据)的一种方式。

它包含了两种相互独立又相互关联的部分:基本数据对齐和结构体数据对齐

为什么要内存对齐

百度百科

iOS 结构体内存对齐

每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令 #pragma pack(n),n=1, 2, 4, 8,16 来改变这一系数,其中的 n 就是你要指定的“对齐系数”。iOS 默认 8

对齐原则

我们定义两个结构体,分别计算他们的内存大小

    struct MyStruct1 {
        double a;//8 (0, 1, 2, 3, 4, 5, 6, 7)
        char b;// 1 (8)
        int c; // 4 /*9, 10, 11 不是4的倍数,所以废弃*/(12, 13, 14, 15)
        short d;// 2 (16, 17)
    } struct1;

    struct MyStruct2 {
        int c;// 4 (0, 1, 2, 3)
        char b;// 1 (4)
        short d;// 2 (6, 7)
        double a;// 8 (8, 9, 10, 11, 12, 13, 14, 15)
    } struct2;

    NSLog(@"%lu, %lu", sizeof(struct1), sizeof(struct2));

输出结果如下

两个结构体的成员个数一样,类型一样,唯一的区别是成员的顺序不一样,它们占用的内存大小就不相等,这就是iOS 中结构体内存对齐。

各种数据类型在 iOS 中的占用内存大小如下图

MyStruct1 的内存存储示意图如下

MyStruct2 的内存存储示意图如下

下面我们再来看下结构体嵌套结构体的内存存储情况,借助上述的两个结构体变量,如下所示

    struct MyStruct3 {
        struct MyStruct1 one;//24 /*结构体中最大元素为 8(double),从 8 的倍数位置存储(offset 为0)*/(0-23)
        struct MyStruct2 two;//16 /*结构体中最大元素为 8(double),24 是 8 的倍数,开始存储*/(24-39)
        int a;// 4 (40, 41, 42, 43)
        double b;// 8 /*44, 45, 46, 47 不是8的倍数,废弃*/ (48, 49, 50, 51, 52, 53, 54, 55)
    } struct3;

    NSLog(@"%lu", sizeof(struct3));

最终的打印结果为 56

MyStruct3 的内存存储示意图如下

以上可以看出,结构体的内存对齐是按照成员类型上下读取的,这就是为什么两个结构体的成员一样而顺序不一样,他们的内存大小就会不一样的原因

iOS 对象的内存对齐

首先,创建一个工程,创建 Person 类,为 Person 类添加一些属性,如下

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickname;

@property (nonatomic, assign) int age;

@property (nonatomic, assign) char char1;
@property (nonatomic, assign) char char2;

@end

@implementation Person

@end

为属性赋值,设置断点并运行起来,如下

Person *p = [Person alloc];
p.name = @"tlab"; //8
p.nickname = @"lc"; //8
p.hobby = @"学习"; //8
p.age = 18; //4
p.char1 = 'a';//1
p.char2 = 'b';//1
NSLog(@"%@", p);

根据打印的 Person 对象实例,查看对象的内存情况可以在lldb中用 xppo 这些指令来查看

是不是有点懵逼?1. 为什么第一个打印的是 Person, 2. age、char1、char2 哪去了?别急,我们先看下内存情况,调用以下方法

// sizeof(p) 为 p 指针占用的内存大小,class_getInstanceSize([p class]) 对象实际使用的内存大小,malloc_size((__bridge const void *)(p)) 为系统申请的内存大小
NSLog(@"%lu---%lu---%lu", sizeof(p), class_getInstanceSize([p class]), malloc_size((__bridge const void *)(p)));

打印结果为 8---40---48

是不是跟你预想的不一样,这里需要解释一下

-指针占用 8 字节,这个没有什么好说的

现在再去看上面的问题,对象默认的第一个属性是 isa,isa 指向所属的类,所以打印的是 Person

为什么 age、char1、char2 没有打印出来呢?第二个打印的不是 tlab 而是乱码呢?原因是苹果中针对 age、char1、char2 属性的内存进行了重排(内存优化),我们换个方式打印

将 0x0000001200006261 拆分打印就得到了 age、char1、char2,其中 97 和 98 分别是小写字母 a 和 b 的 ASCII 编码。为什么会这样呢?因为 Person 中的 age 是 int 类型占 4 字节,char1 和 char2 分别占 1 个字节,通过 4+1+1 的方式,按照 8 字节对齐,不足补齐的方式存储在同一块内存中。

Person 的内存分布情况如下:

上一篇下一篇

猜你喜欢

热点阅读