美文网首页
OC底层原理03-结构体内存对齐

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

作者: AndyGF | 来源:发表于2020-09-09 01:32 被阅读0次

    我们大家都知道, 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 中的占用内存大小, 根据对应类型来计算结构体中内存大小

    数据类型占用内存表

    相关文章

      网友评论

          本文标题:OC底层原理03-结构体内存对齐

          本文链接:https://www.haomeiwen.com/subject/iassektx.html