内存对齐

269 阅读6分钟

1.获取内存大小的三种方式

1.1 sizeof

1.sizeof是一个操作符,不是函数
2.我们一般用sizeof计算内存大小时,传入的主要对象是数据类型,这个在编译器的编译阶段(即编译时)就会确定大小而不是在运行时确定。
3.sizeof最终得到的结果是该数据类型占用空间的大小

1.2 class_getInstanceSize

1.这个方法是runtime提供的api,用于获取类的实例对象所占用的内存大小,并返回具体的字节数,     其本质就是获取实例对象中成员变量的内存大小
2.成员变量会影响当前的对象内存大小,方法不会影响

1.3 malloc_size

 malloc_size:计算对象实际分配的内存大小

结构体内存对齐

struct Mystruct1{
    char a;     //1字节
    double b;   //8字节
    int c;      //4字节
    short d;    //2字节
}Mystruct1;

struct Mystruct2{
    double b;   //8字节
    int c;      //4字节
    short d;    //2字节
    char a;     //1字节
}Mystruct2;

//计算 结构体占用的内存大小
NSLog(@"%lu-%lu",sizeof(Mystruct1),sizeof(Mystruct2));

24-16 他们的内存大小并不相同,这是为什么呢? 我们可以看到这两个结构体的有很大相似程度,只有内部变量的顺序不同,从而导致两者内存并不相同,这就是iOS中的内存对齐原则.

内存对齐的原则主要有3点

1. 数据成员的对齐规则可以理解为min(m, n) 的公式, 其中 m表示当前成员的开始位置, n表示当前成员所需要的位数。如果满足条件 m 整除 n (即 m % n == 0),  n 从 m 位置开始存储, 反之继续检查 m+1 能否整除 n, 直到可以整除, 从而就确定了当前成员的开始位置。
2.数据成员为结构体:当结构体嵌套了结构体时,作为数据成员的结构体的自身长度作为外部结构体的最大成员的内存大小,比如结构体a嵌套结构体bb中有char、int、double等,则b的自身长度为8
3.最后结构体的内存大小必须是结构体中最大成员内存大小的整数倍,不足的需要补齐。

验证对齐规则

根据内存对齐规则计算MyStruct1的内存大小,详解过程如下:

变量a:占1个字节,从0开始,此时min01),即 0 存储 a
变量b:占8个字节,从1开始,此时min18),1不能整除8,继续往后移动,知道min88),从8开始,即 8-15 存储 b
变量c:占4个字节,从16开始,此时min164),16可以整除4,即 16-19 存储 c
变量d:占2个字节,从20开始,此时min(20, 2)20可以整除2,即20-21 存储 d
因此MyStruct1的需要的内存大小为 15字节,而MyStruct1中最大变量的字节数为8,所以 MyStruct1 实际的内存大小必须是 8 的整数倍,18向上取整到24,主要是因为248的整数倍,所以 sizeof(MyStruct1) 的结果是 24

根据内存对齐规则计算MyStruct2的内存大小,详解过程如下:

变量b:占8个字节,从0开始,此时min08),即 0-7 存储 b
变量c:占4个字节,从8开始,此时min84),8可以整除4,即 8-11 存储 c
变量d:占2个字节,从12开始,此时min(12, 2)12可以整除2,即12-13 存储 d
变量a:占1个字节,从14开始,此时min141),即 14 存储 a
因此MyStruct2的需要的内存大小为 15字节,而MyStruct1中最大变量的字节数为8,所以 MyStruct2 实际的内存大小必须是 8 的整数倍,15向上取整到16,主要是因为168的整数倍,所以 sizeof(MyStruct2) 的结果是 16

结论:

结构体内存大小与结构体成员内存大小的顺序有关

如果是结构体中数据成员是根据内存从小到大的顺序定义的,根据内存对齐规则来计算结构体内存大小,需要增加有较大的内存padding即内存占位符,才能满足内存对齐规则,比较浪费内存

如果是结构体中数据成员是根据内存从大到小的顺序定义的,根据内存对齐规则来计算结构体内存大小,我们只需要补齐少量内存padding即可满足堆存对齐规则,这种方式就是苹果中采用的,利用空间换时间,将类中的属性进行重排,来达到优化内存的目的

结构体中嵌套结构体

    double  a; // 8
    int     b; // 4
    short   c; // 2
    char    d; // 1
}LWStruct1;


struct LWStruct2{
    double  a; // 8
    char    d; // 1
    int     b; // 4
    short   c; // 2
 
}LWStruct2;

struct LWStruct3{
    long    a; // 8
    int     b; // 4
    short   c; // 2
    char    d; // 1
    struct LWStruct2 lwStr;
}LWStruct3;

int main(int argc, char * argv[]) {
    @autoreleasepool {
      NSLog(@"-----%lu-----%lu----%lu",sizeof(LWStruct1),sizeof(LWStruct2),sizeof(LWStruct3));
    }
    return 0;
}
 

-----16-----24----40

LWStruct3内存大小详细过程
变量a:8个字节,offert从0开始, min(08), 即0 ~ 7   存放a
变量b:4个字节,offert从8开始, min(84), 即8 ~ 11  存放b
变量c:2个字节,offert从12开始,min(122),即12 ~ 13 存放c
变量d:1个字节,offert从14开始,min(141),即14      存放d
变量lwStr:lwStr是结构体变量,内存对齐原则结构体成员要从其内部最大元素大小的整数倍地址开始存储。LWStruct2 中的最大的变量占8字节,所以offert从16开始,LWStruct2的内存大小是18字节。min(1618),即18 ~ 33存放 lwStr
结果显示 LWStruct3 的实际的内存大小是34字节,LWStruct3中最大的变量是lwStr和 a都是 8 字节。所以LWStruct3的实际内存大小必须是8的整数倍,34不是8的整数倍,向上取整,不足的自动补齐为40字节。最后LWStruct3的内存大小为40字节。

内存优化

  1. 大部分的内存都是通过固定的内存块进行读取,
  2. 尽管我们在内存中采用了内存对齐的方式,但并不是所有的内存都可以进行浪费的,苹果会自动对属性进行重排,以此来优化内存 3.苹果一般才去内存对齐是16字节对齐,因为8字节对齐太紧凑,16字节更容易扩展