IOS底层-struct的内存对齐

1,795 阅读5分钟

RT

1.什么是内存对齐

内存对齐是指,对于大小为x byte的变量,它的内存地址必须是** x 的整数倍**

关于结构体的内存对齐规则,这里不再赘述

2.为什么要内存对齐

内存对齐是为了满足CPU访问内存中数据的效率问题

CPU访问内存需要遵循一定规则

  • 对于64位系统,CPU每次能从内存中拿出对齐的大小为8个字节的内存

比如 [0-7],[16-23],[32-39]......

而内存对齐能够提高CPU访问数据的效率,这就是内存对齐的原因

Intel的x86没有内存对齐的要求,但是内存对齐能提高效率

ARM架构要求必须对齐,否则崩溃

3.内存对齐如何提高效率?

我们用一张图来模拟CPU实际访问结构体中变量

假设某结构体中有三个变量,long(8byte),char(1byte),int(4byte), 现在CPU要访问 char (图中绿色色块)

我们计算从结构体的地址0开始,CPU需要访问的次数

  • 在内存不对齐的情况下,CPU需要访问5次:[0-7]->[8-15]->[7-11]->[7-9]->[7-8]。
  • 在内存对齐的情况下,CPU只需要访问3次:[0-7]->[8-15]->[7-11]。

分析

  • 因为需要内存对齐,int 变量不能从第9个byte(地址8)开始,而是必须从下一个4byte对齐的位置开始,也就是第12个byte(地址11)
  • int 的内存4byte对齐给 char 留下了3byte的padding,它独享对齐的4个字节区域
  • 有了这个独享的 moment,CPU先读了2次8字节对齐,然后只需把读取的宽度从8byte->4byte,一次就能读到 char

结构体嵌套的内存大小

结构体的字节对齐规则有一条:

  • 结构体的内存大小必须是它内部最大变量byte数的整数倍 比如上一个例子,8+4+1=15,但是结构体实际上占用了16个byte,即[0-15]

那嵌套的时候如何计算内存?

  • 假设我们有结构体S1,它内部嵌套了结构体S2
  • 嵌套的时候,S1的“最大属性”是按照谁来决定? S2? S1的最大属性? S2的最大属性?
  • 嵌套的是一个*S2怎么办?

有很多猜测,直接动手看看哪种猜测是对的

    1. 最大属性: 8byte(属于S2)
typedef struct {
    double a;  //最大属性:8byte
    int b;
    char c;
    short d;
} S2;
//sizeof(S2) = 16

typedef struct {
    char a;
    S2 s;
} S1;
//sizeof(S1) = 24
    1. 最大属性: 8byte (属于S1)
typedef struct {
    char c;
} S2;
//sizeof(S2) = 1

typedef struct {
    long a;  //最大属性:8byte
    S2 s;
} S1;
//sizeof(S1) = 16
    1. 最大属性: 4byte (属于S2)
typedef struct {
    int b;   //最大属性:4byte
    char c;
    short d;
} S2;
//sizeof(S2) = 8

typedef struct {
    char a;
    S2 s;
} S1;
//sizeof(S1) = 12
    1. 最大属性: S2的指针
typedef struct {
    char c;
    short d;
} S2;
//sizeof(S2) = 4

typedef struct {
    char a;
    S2 *s;  //最大属性:8byte
} S1;
//sizeof(S1) = 16
    1. 多层套娃: 还是按照全部拆成基本单元后最大的属性来
typedef struct {
    char c;
} S3;
//sizeof(S3) = 1

typedef struct {
    long l;
    char c;
    short d;
    S3 s;
} S2;
//sizeof(S2) = 16

typedef struct {
    char a;
    S2 s;
} S1;
//sizeof(S1) = 24

小结:

    1. 结构体的字节对齐:按照内部最大变量的整数倍进行对齐
    1. 内部嵌套的结构体不能作为一个整体看待,而是要拆看来看属性
    1. 指针是8byte的变量

无论套娃多少层,都全部拆分成最小属性单元,再来找最大属性,按照此大小安排最外层结构体的大小

如果套娃n层(Sn->Sn-1->Sn-2->...S2->S1,n>=2),最外层计为0。最大属性在第k层(k<=n)。则对于第i层的结构体

  • i>k, 第i层的结构体大小找[i, n]层区间内的最大属性
  • i<=k, 第i层的结构体大小找第k层区间的最大属性
  • 树状套娃也是同理

一个坑点

举个栗子

typedef struct {
    int b;   //最大属性:4byte
    int a;
    char c;
    short d;
} S2;
//sizeof(S2) = 12

typedef struct {
    char a;
    S2 s2;
    short b;
} S1;
//sizeof(S1) = 20

S1的声明顺序为:

    1. char a
    1. S2 s
    1. short b

S1 一共20byte

如果改变S1的声明顺序为:

    1. S2 s
    1. char a
    1. short b
typedef struct {
    int b;   //最大属性:4byte
    int a;
    char c;
    short d;
} S2;
//sizeof(S2) = 12

typedef struct {
    S2 s2;
    char a;
    short b;
} S1;
//sizeof(S1) = 16

S1 只有16byte

猜测

被嵌套的结构体的的内存地址必须是它内部的最大变量大小的整数倍

  • char--S2--short 排列时 S2作为一个整体,它的起点必须满足为 int(4byte)的整数倍,所以留下了 3byte的 padding

char[0]

padding[1 3]

S2[4 15]

short[16 17]

padding[18 19]

  • S2--char--short 排列时

S2[0 11]

char[12]

short[13 14]

padding[15]

总结

通过这个例子我们发现结构体在父结构体内部要满足的内存对齐规则和普通变量有所不同。

类型变量结构体
长度xx
最大变量长度自己y
内存对齐原则x字节对齐y字节对齐

这也很好理解

  • 变量是一个整体无法拆分,内存对齐的最小粒度是x
  • 结构体长度x是最大变量长度y的整数倍,内存对齐的最小粒度是y