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

前言

结构体是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++结构体内存对齐分析)