美文网首页
iOS 之C++结构体内存对齐分析

iOS 之C++结构体内存对齐分析

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

    前言

    结构体是c/c++比较常见的数据结构,研究它对于深入学习Object-C也是比较重要的一环。一般的结构体的内存构成不是属性连续的存储,这其中存在着内存对齐。为什么要对齐呢?其根本原因在于提高CPU读取内存的效率。

    一、准备

    1、C、OC基本数据类型占字节数对照表

    C OC 32位 64位
    bool BOOL(64位) 1 1
    signed char (__signed char)int8_t、BOOL(32位) 1 1
    unsigned char Boolean 1 1
    short int16_t 2 2
    unsigned short unichar 2 2
    int int32_t NSInteger(32位)、boolean_t(32位) 4 4
    unsigned int boolean_t(64位)、NSUInteger(32) 4 4
    long NSInteger(64位) 4 8
    unsigned long NSUInteger(64位) 4 8
    long long int64_t 8 8
    float CGFloat(32位) 4 4
    double CGFloat(64位) 8 8
    pointer pointer 4 8

    2、三条对齐原则

    ① 、数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第
    一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要
    从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,
    结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存
    储。
    ②、结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从
    其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b
    里有char,int ,double等元素,那b应该从8的整数倍开始存储.)
    ③、收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大
    成员的整数倍.不足的要补⻬。

    二、分析

    1、我们先来看两个简单的结构体

    struct LGStruct1 {
        double a;
        char b; 
        int c;  
        short d;
    }struct1;
    
    struct LGStruct2 {
        double a;
        int b;
        char c;
        short d;
    }struct2;
    
    struct LGStruct3 {
        double a;
        int b;
        struct LGStruct2 sub_struct;
        char c;
        short d;
    }struct3;
    

    2、手动分析(都是从0字节开始分析)

    • 对于结构体LGStruct1: =>algin(?+ ? + ? + ?)

      分:计算a、b、c、d属性

        a:第一个数据,从0开始排列,由于是double类型,根据上面的对照表需要8个字节,所以内存字节序号区间为[0,7];=>algin(8+ ? + ? + ?)
        b:根据对照表知道它需要1个字节,有根据上面的原则①知道,它应该放在8号位;=>algin(8+ 1 + ? + ?)
        c:需要4个字节,由于原则①,9、10、11号位都不能填数据,所以对饮内存字节序号为[12,15];=>algin(8+1+(3+4)+?)
        d:需要2个字节,由于原则①,对应内存字节序号区间为[16,17];=>algin(8+1+(3+4)+2);
      

      合:但是,algin(18)需要根据原则③,进行总体内存对齐;我们知道LGStruct1里面a数字所需字节数为8,是最多的,8大于18的最小倍数是24,所以LGStruct1结构体产生的实例struct1所需内存大小为24

    • 对于结构体LGStruct2,我们应用上面相同的流程计算可以得到其内存所需大小为16

    • 说了这么多都不如运行一下验证我们的推理来的实在,运行结果如下


      image.png

    怎么样,是不是一模模,一样样;看来我们的过程初步来看是没有问题的。

    • 接下来我们来推一下LGStruct3,结构体中嵌套结构体;=>algin(? + ? + ? + ? + ?)
      分: a, b,sub_struct, c, d属性计算

      a : 需要8字节,对应区间[0,7]; => algin(8+ ? + ? + ? + ?)
      b : 需要4字节,对应区间[8, 11]; => algin(8+ 4 + ? + ? + ?)
      sub_struct: 需要16字节,由于原则②,对应的区间为[16, 31];=> algin(8+ 4 + (4 + 16) + ? + ?)
      c : 需要1字节,区间[32,32]; => algin(8+ 4 + (4 + 16) + 1 + ?)
      d : 需要2字节,区间[34, 35];=> algin(8+ 4 + (4 + 16) + 1 + (1 + 2))
      

      总: 因为内存最大成员为double8字节,所以algin(36) = 40,我们看看运行结果吧!

      image.png

    看来这一步应该也是正确的。
    3、我只到现在我们手动分析了一下,肯定不能满足我们的求知欲;难道大家就没有想过:在内存中结构体真的是按照我们推理的这样排列的吗?不符就干,上图。我们来动态调试分析


    image.png
    • 我们通过lldb 调试 打印struct 的首地址 p &struct1 : 0x000000010b69d9a0;
    • 我们来x/8gx 一下这个首地址:0x000000010b69d9a0;得到一下内容
    0x10b69d9a0: 0x3ff0000000000000 0x0000000300000061
    0x10b69d9b0: 0x0000000000000004 0x3ff0000000000000
    0x10b69d9c0: 0x0005006200000002 0x0000000000000000
    0x10b69d9d0: 0x0000000000000000 0x0000000000000000
    

    1)第一个双字(QW): 0x3ff0000000000000 ,对应的应该就是struct1中的a的内存内容

    00111111 11111111 00000000 00000000 00000000 00000000 00000000 00000000  //内容已经大端排列, 二进制展示
    

    对应的double类型

    符号位:0
    指数部分:1023 - 1023 = 0
    尾数部分:0
    

    为什么这么分解请参照浮点类型
    求解出来不就是1吗?这里对应的字节区间为[0,7]

    1. 第二个双字(QW): 0x0000000300000061,拆分到内存字节序为

      <61, 00, 00, 00, 03, 00, 00, 00> //内容已经小端排列,16进制展示
      

    这里肯能就会疑惑,为什么上一个double类型分析,大端排列,这里又小端排列?
    答:double类型分析,分析的是已经是double类型整体8字节内容。这里小端是内存的字节序,也就叫主机序(不一样的cpu可能不一样的主机序啊,现在一般都是小端),我们是要进行拆分来分析的。

    0x61不就是字符aascii吗,对应的就是struct1中的b属性,这是第8号位,区间[8,8];接下来的区间[9,11]可以看到都是存放的0x00,到了12号位才有数据;我们知道接下来我们需要读取一个int 4字节内容
    也就是区间[12, 15]内容<03, 00, 00, 00>转换到大端排列,就是数值为3的struct1中的c中的值。
    3)第三个双字(QW): 0x0000000000000004,拆分到内存字节序为

       <04, 00, 00, 00, 00, 00, 00, 00> //内容已经小端排列,16进制展示
    

    我们知道我们接下来要读一个2字节的short,需要读取的内存为<04, 00>, 现在我们应该清晰的认识到这个值为4)对应的是struct1中的d中的值。

    5)第4个双字:0x3ff0000000000000对应struct2中的a的值;

    6)第五个双字:0x0005006200000002

     <02, 00, 00, 00, 62, 00, 05, 00>
    

    根据前序步骤,聪敏的你们一定可以分析出这里的内容分别是

    struct2.b = 2;
    struct2.c = 'b';
    struct2.d = 5;
    

    三、 总结

    结构体的内存布局,抓住3个原则,就可以递归的分析所有的结构体了。了解了结构体的内存结构,有助于我们理解Object-C进行编译的时候为什么会属性重排,其实就是为了提高存储能力。

    相关文章

      网友评论

          本文标题:iOS 之C++结构体内存对齐分析

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