mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 04:17:17 +00:00
297 lines
10 KiB
Markdown
297 lines
10 KiB
Markdown
# Swift 枚举值内存布局
|
||
|
||
> enum 使用很简单,那大家有没有思考过系统针对枚举的实现是怎么样的?接下去会针对不同情况的枚举,结合汇编来窥探下系统实现原理。
|
||
|
||
### 基础枚举
|
||
|
||
```swift
|
||
enum Season {
|
||
case spring
|
||
case summer
|
||
case antumn
|
||
case winter
|
||
}
|
||
|
||
var season: Season = Season.spring
|
||
print(Mems.ptr(ofVal: &season))
|
||
season = Season.summer
|
||
season = Season.antumn
|
||
print("over")
|
||
```
|
||
|
||
- `var season: Season = Season.spring` 基础枚举,默认值是0。
|
||
|
||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/EnumBaseMemoryLayoutDemo1.png" style="zoom:25%">
|
||
|
||
- `season = Season.summer`,此时可以看到第一个字节的位置是1.
|
||
|
||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/EnumBaseMemoryLayoutDemo2.png" style="zoom:25%">
|
||
|
||
- `season = Season.antumn` ,此时可以看到第一个字节的位置是2
|
||
|
||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/EnumBaseMemoryLayoutDemo3.png" style="zoom:25%">
|
||
|
||
结论:查看内存信息,可以看到基础枚举,只占1个字节大小空间,且值为默认值。
|
||
|
||
### 只有原始值
|
||
|
||
```swift
|
||
enum Season:Int {
|
||
case spring = 1
|
||
case summer = 2
|
||
case antumn = 3
|
||
case winter = 4
|
||
}
|
||
|
||
//print(MemoryLayout<Season>.size)
|
||
//print(MemoryLayout<Season>.stride)
|
||
//print(MemoryLayout<Season>.alignment)
|
||
|
||
var season: Season = Season.spring
|
||
print(Mems.ptr(ofVal: &season))
|
||
season = .summer
|
||
season = .winter
|
||
print("over")
|
||
```
|
||
|
||
- `var season: Season = Season.spring` 基础枚举,变量默认值,可以看到第一个字节的位置是0
|
||
|
||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/EnumWithRawValueMemoryLayoutDemo1.png" style="zoom:25%">
|
||
|
||
- `season = .winter` 基础枚举,当赋值为 winter 的时候,可以看到第一个字节的位置是3
|
||
|
||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/EnumWithRawValueMemoryLayoutDemo2.png" style="zoom:25%">
|
||
|
||
结论:带有原始值的枚举,同样只占用1个字节,该字节的值为枚举的位置(比如 case1 case2)
|
||
|
||
|
||
|
||
### 带有关联值的枚举
|
||
|
||
```swift
|
||
enum Season {
|
||
case spring(Int, Int, Int)
|
||
case summer(Int, Int)
|
||
case antumn(Int)
|
||
case winter(Bool)
|
||
case unknown
|
||
}
|
||
|
||
print(MemoryLayout<Season>.size)
|
||
print(MemoryLayout<Season>.stride)
|
||
print(MemoryLayout<Season>.alignment)
|
||
|
||
var season: Season = Season.spring(1, 2, 3)
|
||
print(Mems.ptr(ofVal: &season))
|
||
season = Season.summer(4, 5)
|
||
season = Season.antumn(6)
|
||
season = Season.winter(true)
|
||
season = Season.unknown
|
||
print("over")
|
||
```
|
||
|
||
- `var season: Season = Season.spring(1, 2, 3)` 带有关联值的枚举,`.spring` 有3个 Int,单个 Int 占8个字节空间,所以红色框代表 spring 的1,蓝色框代表 spring 的2,绿色框代表 spring 的3,黄色框代表枚举的第1个 case,剩余7个字节,为空。
|
||
|
||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/AssociatedEnumMemoryLayoutDemo1.png" style="zoom:25%">
|
||
|
||
其内存信息如下(8字节为1组,对应上图)
|
||
|
||
```shell
|
||
01 00 00 00 00 00 00 00
|
||
02 00 00 00 00 00 00 00
|
||
03 00 00 00 00 00 00 00
|
||
00
|
||
00 00 00 00 00 00 00
|
||
```
|
||
|
||
这段内存信息怎么看?我划分了下
|
||
|
||
```
|
||
关联值: 01 00 00 00 00 00 00 00
|
||
关联值: 02 00 00 00 00 00 00 00
|
||
关联值: 03 00 00 00 00 00 00 00
|
||
位置值: 00
|
||
内存对齐占用:00 00 00 00 00 00 00
|
||
```
|
||
|
||
下面的几组一样
|
||
|
||
- `season = Season.summer(4, 5)` 带有关联值的枚举,`.summer` 有2个 Int,单个 Int 占8个字节空间,所以红色框代表 summer 的4,蓝色框代表 summer 的5,绿色框为空,黄色框代表枚举的第2个 case,剩余7个字节,为空。
|
||
|
||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/AssociatedEnumMemoryLayoutDemo3.png" style="zoom:25%">
|
||
|
||
其内存信息如下(8字节为1组,对应上图)
|
||
|
||
```shell
|
||
04 00 00 00 00 00 00 00
|
||
05 00 00 00 00 00 00 00
|
||
00 00 00 00 00 00 00 00
|
||
01
|
||
00 00 00 00 00 00 00
|
||
```
|
||
|
||
- `season = Season.antumn(6) 带有关联值的枚举,`. `antumn` 有1个 Int,单个 Int 占8个字节空间,所以红色框代表 antumn 的6,蓝色框为空,绿色框为空,黄色框代表枚举的第3个 case,剩余7个字节,为空。
|
||
|
||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/AssociatedEnumMemoryLayoutDemo4.png" style="zoom:25%">
|
||
|
||
其内存信息如下(8字节为1组,对应上图)
|
||
|
||
```shell
|
||
06 00 00 00 00 00 00 00
|
||
00 00 00 00 00 00 00 00
|
||
00 00 00 00 00 00 00 00
|
||
02
|
||
00 00 00 00 00 00 00
|
||
```
|
||
|
||
- `season = Season.winter(true)` 带有关联值的枚举,`. `winter` 有1个 Bool,单个 Int 占1个字节空间,所以红色框代表 winter 的 true,蓝色框为空,绿色框为空,黄色框代表枚举的第4个 case,剩余7个字节,为空。
|
||
|
||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/AssociatedEnumMemoryLayoutDemo5.png" style="zoom:25%">
|
||
|
||
其内存信息如下(8字节为1组,对应上图)
|
||
|
||
```shell
|
||
01 00 00 00 00 00 00 00
|
||
00 00 00 00 00 00 00 00
|
||
00 00 00 00 00 00 00 00
|
||
03
|
||
00 00 00 00 00 00 00
|
||
```
|
||
|
||
- `season = Season.unknown` 带有关联值的枚举,`unknown` 没有关联值,所以红色框为空,蓝色框为空,绿色框为空,黄色框代表枚举的第5个 case,剩余7个字节,为空。
|
||
|
||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/AssociatedEnumMemoryLayoutDemo6.png" style="zoom:25%">
|
||
|
||
其内存信息如下(8字节为1组,对应上图)
|
||
|
||
```shell
|
||
00 00 00 00 00 00 00 00
|
||
00 00 00 00 00 00 00 00
|
||
00 00 00 00 00 00 00 00
|
||
04
|
||
00 00 00 00 00 00 00
|
||
```
|
||
|
||
- `MemoryLayout<Season>.size` :3个 Int 最大为3*8,1个字节用来表达位置信息,`3*8 + 1 = 25`
|
||
- `MemoryLayout<Season>.stride` :获取系统分配给数据类型的内存大小,也就是实际内存大小(对齐后的)
|
||
- `MemoryLayout<Season>.alignment` 内存对齐系数,以8 Byte 为单位,对象分配的内存必须是该值的整数倍
|
||
|
||
|
||
|
||
### 只有一个 case 的枚举
|
||
|
||
```swift
|
||
enum SimpleEnum {
|
||
case one
|
||
}
|
||
var caseOne = SimpleEnum.one
|
||
print(MemoryLayout<SimpleEnum>.size) // 0
|
||
print(MemoryLayout<SimpleEnum>.stride) // 1
|
||
print(MemoryLayout<SimpleEnum>.alignment) // 1
|
||
```
|
||
|
||
为什么 size 为0?看上去是一个变量,但根本不占内存。因为枚举里面就一个 case,所以里面根本不需要存储值来区分是哪个 case。
|
||
|
||
```swift
|
||
enum SimpleEnum {
|
||
case one
|
||
case two
|
||
}
|
||
var caseOne = SimpleEnum.one
|
||
print(MemoryLayout<SimpleEnum>.size) // 1
|
||
print(MemoryLayout<SimpleEnum>.stride) // 1
|
||
print(MemoryLayout<SimpleEnum>.alignment) // 1
|
||
```
|
||
|
||
现在好理解,2个 case 需要存储1个 Byte 的值来区分是哪个 case,1 Byte 可以代表最多256个 case
|
||
|
||
### 只有1个 case 且带关联值的枚举
|
||
|
||
```swift
|
||
enum SimpleEnum {
|
||
case one(Int)
|
||
}
|
||
var caseOne = SimpleEnum.one(4)
|
||
print(MemoryLayout<SimpleEnum>.size) // 8
|
||
print(MemoryLayout<SimpleEnum>.stride) // 8
|
||
print(MemoryLayout<SimpleEnum>.alignment) // 8
|
||
```
|
||
|
||
带有关联值且只有1个 case 的枚举,因为有1个 Int 的关联值,但只有1个 case,所以只需要8 Byte 存储关联值即可。
|
||
|
||
请看下面的对照实验
|
||
|
||
```swift
|
||
enum SimpleEnum {
|
||
case one(Int)
|
||
case two
|
||
}
|
||
var caseOne = SimpleEnum.one(4)
|
||
print(MemoryLayout<SimpleEnum>.size) // 9
|
||
print(MemoryLayout<SimpleEnum>.stride) // 16
|
||
print(MemoryLayout<SimpleEnum>.alignment) // 8
|
||
```
|
||
|
||
2个 case,其中一个 case 有关联值 Int,所以需要8 Byte 存 Int 值,1 Byte 区分是哪个 case,实际需要占用 8 + 1 = 9 Byte,内存对齐单位是8,9向上为16.
|
||
|
||
|
||
|
||
### 用汇编验证下内存
|
||
|
||
```
|
||
enum Season {
|
||
case spring(Int, Int, Int)
|
||
case summer(Int, Int)
|
||
case antumn(Int)
|
||
case winter(Bool)
|
||
case unknown
|
||
}
|
||
|
||
var season: Season = Season.spring(1, 2, 3)
|
||
print(Mems.ptr(ofVal: &season))
|
||
season = Season.summer(4, 5)
|
||
season = Season.antumn(6)
|
||
season = Season.winter(true)
|
||
season = Season.unknown
|
||
print("over")
|
||
```
|
||
|
||
断点停到 `var season: Season = Season.spring(1, 2, 3)` 位置
|
||
|
||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/AssociatedEnumMemoryLayoutExplore.png" style="zoom:25%">
|
||
|
||
将断点处的汇编单独摘出来研究
|
||
|
||
```assembly
|
||
0x10000334b <+11>: movq $0x1, 0x8eaa(%rip) ; demangling cache variable for type metadata for Swift.Array<Swift.UInt8> + 4
|
||
0x100003356 <+22>: movq $0x2, 0x8ea7(%rip) ; SwiftDemo.season : SwiftDemo.Season + 4
|
||
0x100003361 <+33>: movq $0x3, 0x8ea4(%rip) ; SwiftDemo.season : SwiftDemo.Season + 12
|
||
0x10000336c <+44>: movb $0x0, 0x8ea5(%rip) ; SwiftDemo.season : SwiftDemo.Season + 23
|
||
0x100003373 <+51>: movl $0x1, %edi
|
||
```
|
||
|
||
`rip` 存储的说指令的地址。CPU 要执行的下一条指令地址就存储在 rip 中。所以在执行第一行的时候,rip 寄存器的值。
|
||
|
||
所以第一句汇编代码的意思是:rip 为 `0x100003356`,再加上 `0x8eaa`,得到一个地址值(用 Mac 自带的计算器可以算出)`0X10000C200`,然后 movq 是将十六进制的1赋值给 `0X10000C200` 这个地址。
|
||
|
||
第二句汇编代码类似,此时 rip 为 `0x100003361`,再加上 `0x8ea7`,得到一个地址值 `0X10000C208`,然后 movq 将十六进制的2赋值给 `0X10000C208` 这个地址。
|
||
|
||
第三句汇编代码类似,此时 rip 为 `0x10000336c`,再加上 `0x8ea4`,得到一个地址值 `0X10000C210`,然后 movq 将十六进制的3赋值给 `0X10000C210` 这个地址。
|
||
|
||
第四句汇编代码类似,此时 rip 为 `0x100003373`,再加上 `0x8ea5`,得到一个地址值 `0X10000C218`,然后 movq 将十六进制的0赋值给 `0X10000C218` 这个地址。
|
||
|
||
此时断点走到下一行,拿到 season 的内存地址 `0X10000C200` ,查看内存发现和上面理论分析一直
|
||
|
||
```shell
|
||
01 00 00 00 00 00 00 00
|
||
02 00 00 00 00 00 00 00
|
||
03 00 00 00 00 00 00 00
|
||
00 00 00 00 00 00 00 00
|
||
```
|
||
|
||
结论:如果枚举存在关联值,内存大小为:
|
||
- 1个字节用来存储成员值
|
||
- n个字节用来存储关联值(n取占用内存最大的关联值),任何一个 case 的关联值都共用这 n 个字节
|
||
- 且存在内存对齐,所以占用大小为 n 和 1 的最大值,再结合内存对齐。
|
||
- 如果枚举的定义非常简单,系统会用1个字节来存放值,最大范围是256个 case。
|
||
- 枚举定义如果有原始值,也不会影响内存布局。 |