iOS底层原理之内存对齐探索

672 阅读5分钟

在iOS开发中,我们对于对象的内存空间大小并不是很了解,一个对象开辟多少的存储空间并不清楚,本文我们探索下内存空间大小是怎么分配的。

准备工作

iOS中各种数据类型所占的内存大小

计算内存大小的方法

  • sizeof():获得数据类型的大小,比如

    //char占1位字节,所以a = 1
            int a = sizeof(char);
            //long占8位字节,所以b = 8
            int b = sizeof(long);
            //int占4位字节,所以c = 4
            int c = sizeof(int);
    

  • class_getInstanceSize():计算对象实际占用内存空间大小,实际例子下面会详细讲到

  • malloc_size():计算系统最终给对象开辟的内存空间大小,我会在下面跟class_getInstanceSize一起配合讲解

对象内存空间

我们从上文iOS底层之alloc探索中知道当alloc一个没有任何属性的对象的时候,获得内存大小是16字节(isa占用8个字节,剩余8个字节以备扩充使用),那么当这个对象有属性的时候呢?我们以LGPerson对象为例子来研究。

LGPerson一个属性时

代码如下

@property (nonatomic, copy) NSString *name;

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

打印结果为

我们发现sizeof结果是8,因为打印的是person的isa指针的类型,所以是8;

class_getInstanceSize结果是16,因为person对象当没有任何属性的时候是16,其中有8个字节是空余的,恰好这个8个字节被name属性占用,所以是8 + 8 = 16;

malloc_size结果也是16,这是因为person对象实际占用的空间恰好是16,是16的倍数,所以是16。

那么当我们继续添加一个int类型的属性的时候,这三个计算结果又是什么呢?

@property (nonatomic, assign) int age; //4

打印结果

我们惊讶的发现sizeof结果没变还是8,class_getInstanceSize结果变成24,因为添加了int属性,它占4个字节,要满足内存8字节对齐,所以是8 + 8 + 8 = 24;

malloc_size结果变成32,由于24不是16的倍数,所以是32。

内存分布区域

我们假设存储从0开始,那么LGPerson对象(有name、age、c1三个属性)的存储空间大概分布如下图所示

对象内存分布总结

  • 对象采用的是16字节对齐方式,系统给对象开辟的空间大小必定是16的倍数
  • 对象所占空间的大小跟对象的属性有关系,对象的属性内存遵循8字节对齐方式,比如name属性从第8个位置开始(NSString占8个字节,8%8 = 0,所以从第8个位置开始,否则从第16个位置开始)

结构体内存探究

结构体代码分析

我们看下面代码

struct LGStruct1 {
    double a;   //8 [0,7]
    char b; //1 [8]
    int c;  //4 [12,15]
    short d;    //2 [16,17]
}struct1;//24

struct LGStruct2 {
    double a;   //8 [0,7]
    int b;  // 4 [8,11]
    char c; // 1 [12]
    short d;    //2 [14,15]
    struct LGStruct1 strct; //24 [16,39]
}struct2;//40
NSLog(@"struct1 = %lu",sizeof(struct1));
NSLog(@"struct2 = %lu",sizeof(struct2));

上面我们定义了两个结构体struct1、struct2,其中每个结构体成员所占的字节数以及对应位置已经标记上面,我们看打印结果

结构体内存总结

我们根据打印结果发现每个结构体所占的空间恰好是我们计算好的。那么结构体内存是怎么分布的呢?

我们先看struct1结构体,struct1结构体中占用字节最多的是double a变量,它占8个字节,所以结构体struct1所占的内存大小一定是8的倍数,因此struct1结构体是24;

我们再看struct2结构体,它的变量中有struct1结构体,struct2中占用最多的依然是double a变量,所以struct2所占的内存大小也一定是8的倍数,由于它包含了结构体struct1,所以它需要加上struct1所占的字节数,估是40。

内存对齐总结

  • 数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组, 结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存 储。 min(当前开始的位置m,n)m=9n=4 其中 m表示当前成员的开始位置, n表示当前成员所需要的位数。如果满足条件 m 整除 n (即 m % n == 0), n 从 m 位置开始存储, 反之继续检查 m = m+1 能否整除 n, 直到可以整除, 从而就确定了当前成员的开始位置。
  • 数据成员为结构体:当结构体嵌套了结构体时,作为数据成员的结构体的自身长度作为外部结构体的最大成员的内存大小,比如结构体a嵌套结构体b,b中有char、int、double等,则b的自身长度为8

  • 结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补⻬