OC底层原理03-结构体内存对齐

2020-09-09  本文已影响0人  AndyGF

我们大家都知道, Object-C (简称 OC) 类实际就是结构体, 如果我们想搞清楚OC对象的内存原理, 就必须先搞清楚结构体在内存中的存储情况, 比如占用的内存空间, 成员存储规则.

所以今天先来搞清楚结构体的内存情况, 大家都知道, 结构体的成员还可以是结构体, 就是结构体嵌套, 所以从是否嵌套结构体成员的角度出发, 本次我们分两大块来探讨:

探索环境: 64 位系统

无嵌套结构体

把结论写在最前边, 要明确一点, 一个结构体的内存空间是连续的.

无嵌套结构体内存分配规则:

  1. 结构体(struct)的数据成员,第一个数据成员放在 offset 为 0 的地方,以后每个数据成员存储的起始位置, 要从该成员占内存大小的整数倍下标开始存储。
  2. 结构体的总大小,也就是 sizeof 的结果, 必须是其内部最大成员占内存大小的整数倍. 不足的要补⻬。

接下来, 我们举例说明以上结论

首先我们先定义两个结构体, 这两个结构体成员相同, 但是成员声明的顺序不同. 然后打印他们占用内存的大小

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


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

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    NSLog(@"Struct1 的内存: %lu", sizeof(Struct1));
    NSLog(@"Struct2 的内存: %lu", sizeof(Struct2));
}

输出结果如下图:


无嵌套两个结构体内存大小

从图中我们可以看到, 两个结构体占用的内存大小是不同的, 而这两个结构体只有成员的顺序不同, 那么显然是和顺序有. 下面我们就根据以上两点规则来推演一下, 看看是不是和打印的结果相同.

两个结构体的分析图

Struct1

  1. 成员 a 占用的内存是从 Struct1 的首地址偏移( offset) 0 开始, double 类型占8个字节, 所以成员 a占用的空间 为 0 ~ 7.
  2. 成员 c 占用的内存的 offset8开始, char 类型占 1 个字节, 下标 81 的整数倍.所以 成员c 的占用的空间为 8.
  3. 成员 b 占用的内存的 offset9开始, int 类型占 4 个字节, 下标 9 不是 4 的整数倍. 那就继续向后查, 最小的4的倍数就是12, 所以 成员b 的占用的空间为 12 ~ 15.
  4. 成员 d 占用的内存的 offset16开始, short 类型占 2 个字节, 下标 162 的整数倍.所以 成员d 的占用的空间为 16 ~ 17.
  5. 按第二条规则, Struct1 中最大的成员是 a, double类型, 占8个字节, 因此总大小必须是 8 的整数倍, 而此时 0 ~ 17, 一共占用 18 个字节的空间. 18 并不是 8 的整数倍, 因此Struct1分配的空间只能是更大些, 比 18 大, 同时又是 8 的倍数的最小整数就是 24.

Struct2

  1. 成员 a 占用的内存是从 Struct1 的首地址偏移( offset) 0 开始, double 类型占8个字节, 所以成员 a占用的空间 为 0 ~ 7.
  2. 成员 b 占用的内存的 offset8开始, int 类型占 4 个字节, 下标 8 正好是 4 的整数倍. 所以 成员b 的占用的空间为 8 ~ 11.
  3. 成员 c 占用的内存的 offset12开始, char 类型占 1 个字节, 下标 121 的整数倍.所以 成员c 的占用的空间为 12.
  4. 成员 d 占用的内存的 offset13开始, short 类型占 2 个字节, 下标 13 不是 2 的整数倍. 那就继续向后查, 最小的2的倍数就是14, 所以 成员d 的占用的空间为 14 ~ 15.
  5. 按第二条规则, Struct2 中最大的成员是 a, double类型, 占8个字节, 因此总大小必须是 8 的整数倍, 而此时 0 ~ 16, 一共占用 16 个字节的空间. 16 正好是 8 的整数倍, 因此Struct2分配的空间最小整数就是 16.

通过对 Struct1Struct2 分析, 我们扮演的结果 与 控制台打印结果 相同, 共同验证了上面的结论. 如果你还不相信, 那可以多举例验证.

有嵌套结构体

把结论写在最前边, 非结构体类型成员同上规则, 只是多一条结构体类型成员的规则 .

无嵌套结构体内存分配规则:

  1. 结构体(struct)的成员,第一个成员放在 offset 为 0 的地方,以后每个成员存储的起始位置, 要从该成员占内存大小的整数倍下标开始存储。
  2. 如果一个结构里有结构体类型的成员, 则结构体成员要从其内部最大元素大小的整数倍地址开始存储.
  3. 结构体的总大小,也就是 sizeof 的结果, 必须是其内部最大成员占内存大小的整数倍. 不足的要补⻬。

我们再声明一个结构体 Struct3, 让 Struct3 嵌套在 Struct1Struct2 中:


// 总大小 16
typedef struct {
    short e;     // 2
    double f;    // 8
} Struct3;

// 总大小 40
typedef struct {
    double a;   // 8   0 ~ 7
    char c;     // 1   8
    Struct3 b;  // 8   16 ~ 31  (9 ~ 15 不是 8 的倍数)
    short d;    // 2   32 ~ 33
} Struct1;

// 总大小 32,
typedef struct {
    double a;   // 8   0 ~ 7
    Struct3 b;  // 8   8 ~ 23
    char c;     // 1   24
    short d;    // 2   26 ~ 27  (17 不是 2 的倍数)
} Struct2;

有嵌套分析图和打印结果

这次我们直接看分析图, 推理和无嵌套类似.

有嵌套这种情况要注意一下:

  1. Struct3的总大小是 16, 而不是 10, 按照无嵌套情况的规则计算而来.
  2. 计算 Struct1Struct2 总大小时, 确定最大成员的大小时, 并不是 Struct3 的大小, 而是 所以最初级类型成员的 大小作比较, 如 int, char, double. 所以 Struct1 的大小是 40, Struct2 的大小是 32.

结论:

  1. 结构体(struct)的成员,第一个成员放在 offset 为 0 的地方,以后每个成员存储的起始位置, 要从该成员占内存大小的整数倍下标开始存储。
  2. 如果一个结构里有结构体类型的成员, 则结构体成员要从其内部最大元素大小的整数倍地址开始存储.
  3. 结构体的总大小,也就是 sizeof 的结果, 必须是其内部最大成员占内存大小的整数倍. 不足的要补⻬。
  4. 第 3 条中确定最大成员作比较时, 要包含嵌套类型的成员, 即最初级的 数据类型. 不能再拆分为止.

注意: sizeof是操作符, 而不是函数. 检测的是数据类型占用内存的大小.

下表是各种数据类型在 OC 中的占用内存大小, 根据对应类型来计算结构体中内存大小

数据类型占用内存表
上一篇下一篇

猜你喜欢

热点阅读