# 02.结构体内存对齐

## 一、各类型所占内存对照表

![1](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Ffd5e71b43168870cd105f747d95456b3b6bcd00f.png?generation=1623200405477003\&alt=media)

## 二、结构体内存大小

### 2.1 创建调试用结构体

```C++
struct Girl {
    double height;
    char firstChar;
    int age;
    short hairCount;
}Misa;

struct Boy {
    double height;
    int age;
    char firstChar;
    short hairCount;
}Light;

struct Kira {
    double height;
    int age;
    char firstChar;
    short hairCount;
    struct Girl misa;
    struct Boy light;
}Together;
```

### 2.2 调试输出结构体大小

```C++
(lldb) po sizeof(Misa)
24

(lldb) po sizeof(Light)
16

(lldb) po sizeof(Together)
56
```

### 2.3 思考

奇怪了，`Misa`和`Light`，明明内存成员相同，怎么一个16，一个24呢？下面我们先了解一个东西：`内存对齐`

## 三、内存对齐规则

### 3.1 数据成员对齐规则

* 第一个数据成员从偏移量为0的地方开始，占用该成员大小的字节。
  * 如Misa，以`double`开始，那么就是`0-7`
* 以后每个成员的存储起始位置都要从`该成员大小`或者`成员的子成员`大小的`整数倍`开始
  * 子成员，如：
    * 数组、结构体等

### 3.2 结构体作为成员对齐规则

* 如果一个`结构体A`内，有`结构体B`作为成员。则该成员要从其内部`最大元素大小`的`整数倍地址`开始存储.

### 3.3 总大小对齐

* 结构体总大小，必须是其内部最大成员的整数倍，不足要补齐。

## 四、还原内存结构

### 4.1 Misa的结构

|    -   |      double     | char |     int     | short |
| :----: | :-------------: | :--: | :---------: | :---: |
| 大小（字节） |        8        |   1  |      4      |   2   |
|  使用空间  | 0.1.2.3.4.5.6.7 |   8  | 12.13.14.15 | 16.17 |

* double
  * 8字节
  * 从0开始，占用0-7
* char
  * 1字节
  * 从1的整数倍开始，8满足条件，占用8
* int
  * 4字节
  * 从4的整数倍开始，上一个末位为8，那么先后找，就找从12开始，占用12-15
* short
  * 2字节
  * 从2的整数倍开始，上一个末位为15，那就从16开始，暂用16-17
* 总大小
  * 这些成员暂用了0-16共17个字节
  * 按照第三条对齐规则，向上取最大成员8的倍数，结果就是0-23，即24字节。和实际输出相同

### 4.2 Light的结构

|    -   |      double     |    int    | char | short |
| :----: | :-------------: | :-------: | :--: | :---: |
| 大小（字节） |        8        |     4     |   1  |   2   |
|  使用空间  | 0.1.2.3.4.5.6.7 | 8.9.10.11 |  12  | 14.15 |

* double
  * 8字节
  * 从0开始，占用0-7
* int
  * 4字节
  * 从4的倍数开始，合适的是8，暂用8-11
* char
  * 1字节
  * 从1的倍数开始，合适的是12，占用12
* short
  * 2字节
  * 从2的倍数开始，合适的是14，占用14-15
* 总大小
  * 成员占用了0-15
  * 按照原则3，需要是最大成员8的倍数，即16字节。和实际输出相同

### 4.3 对比Light和Misa

如此我们可以发现，对于结构体来说，内部成员的顺序直接影响了结构体占用的空间。

## 五、嵌套结构体Together的内存结构

### 5.1 Together的外层结构

|    -   |      double     |    int    | char | short |  Girl |  Boy  |
| :----: | :-------------: | :-------: | :--: | :---: | :---: | :---: |
| 大小（字节） |        8        |     4     |   1  |   2   | 成员最大8 | 成员最大8 |
|  使用空间  | 0.1.2.3.4.5.6.7 | 8.9.10.11 |  12  | 14.15 |  ...  |  ...  |

> 由于比较长，Girl和Boy我们拿出来做

### 5.2 Girl

从8的整数倍开始，即16

|    -   |          double         | char |     int     | short |
| :----: | :---------------------: | :--: | :---------: | :---: |
| 大小（字节） |            8            |   1  |      4      |   2   |
|  使用空间  | 16.17.18.19.20.21.22.23 |  24  | 28.29.30.31 | 32.33 |

### 5.3 Boy

从8的整数倍开始，即40

|    -   |          double         |     int     | char | short |
| :----: | :---------------------: | :---------: | :--: | :---: |
| 大小（字节） |            8            |      4      |   1  |   2   |
|  使用空间  | 40.41.42.43.44.45.46.47 | 48.49.50.51 |  52  | 54.55 |

### 5.4 总size

* 占用0-55
* 最大成员8，则Together总大小为56。与输出一致。

## 六、思考

### 6.1 顺序影响大小，我们知道OC对象的本质就是结构体,那么是不是我们在开发的时候要算出最优解，再来排列成员呢？

并不用，这里内部做了优化了的。后面我们会单独对这里进行研究。

### 6.2 做内存对齐的意义何在呢？

#### a.平台因素

* 不是所有硬件平台都能访问任意地址上的任意数据的
* 某些硬件平台只能在某些地址处理某些特定的数据类型，否则会抛出硬件异常

#### b.性能因素

* 数据结构，尤其是栈，应该尽可能的在自然边界上对齐
* 因为未来访问未对齐的内存，处理器需要做两次内存访问；而对其的内存访问只需要一次

#### c.总结

* 内存对齐是一个`以空间换时间`的过程，为了使CPU更高效的读写数据，提升性能

## 七、联合体

* 结构体
  * 优点：所有成员可共存，全面
  * 缺点：内存分配存在浪费，不管用不用，全部分配
* 联合体
  * 优点：内存使用更加精细灵活，节省空间
  * 缺点：成员互斥
  * 同时只有一个成员可以得到这块内存的使用权(对该内存的读写)，各变量共用一个内存首地址
  * 一个union变量的总长度至少能容纳最大的成员变量，而且要满足是所有成员变量类型大小的整数倍

举个例子：游戏中的控制角色移动

```C++
union Dirction {
  bool up;
  bool down;
  bool left;
  bool right;
} move;
```
