update: 动态库、静态库的编译链接细节

This commit is contained in:
FantasticLBP
2025-06-23 01:18:55 +08:00
parent aca020701b
commit 1142064d28
129 changed files with 10932 additions and 2615 deletions

View File

@@ -1,8 +1,12 @@
# Swift 枚举值内存布局
> enum 使用很简单,那大家有没有思考过系统针对枚举的实现是怎么样的?接下去会针对不同情况的枚举,结合汇编来窥探下系统实现原理。
> enum 使用很简单,那大家有没有思考过系统针对枚举的实现是怎么样的?
>
> 不同类型的枚举占用多大内存空间?下面结合汇编来窥探下系统实现原理
### 基础枚举
## 基础枚举(不带关联值、不带原始值)
```swift
enum Season {
@@ -21,22 +25,31 @@ print("over")
- `var season: Season = Season.spring` 基础枚举默认值是0。
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/EnumBaseMemoryLayoutDemo1.png" style="zoom:25%">
<img src="./../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%">
<img src="./../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%">
<img src="./../assets/EnumBaseMemoryLayoutDemo3.png" style="zoom:25%">
结论查看内存信息可以看到基础枚举只占1个字节大小空间且值为默认值
结论:查看内存信息,可以看到**不带关联值、不带原始值的基础枚举只占1个字节大小空间且值为默认值**
### 只有原始值
延伸对于无关联值、无原始值的简单枚举Swift 编译器会进行内存优化:
- 当枚举 case ≤ 256 个时,使用 1 字节UInt8
- 当 case ≤ 65536 时,使用 2 字节UInt16
## 只有原始值的枚举
不带关联值、只有原始值的枚举
```swift
enum Season:Int {
enum Season : Int {
case spring = 1
case summer = 2
case antumn = 3
@@ -56,17 +69,17 @@ print("over")
- `var season: Season = Season.spring` 基础枚举变量默认值可以看到第一个字节的位置是0
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/EnumWithRawValueMemoryLayoutDemo1.png" style="zoom:25%">
<img src="./../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%">
<img src="./../assets/EnumWithRawValueMemoryLayoutDemo2.png" style="zoom:25%">
结论带有原始值的枚举同样只占用1个字节该字节的值为枚举的位置比如 case1 case2
结论:**只带有原始值的枚举同样只占用1个字节该字节的值为枚举的位置索引(比如1、2而非原始值。原始值不占用枚举的内存**
### 带有关联值的枚举
## 带有关联值的枚举
```swift
enum Season {
@@ -90,9 +103,11 @@ 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个字节为空。
- `var season: Season = Season.spring(1, 2, 3)` 带有关联值的枚举
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/AssociatedEnumMemoryLayoutDemo1.png" style="zoom:25%">
`.spring` 有3个 Int单个 Int 占8个字节空间所以红色框代表 spring 的1蓝色框代表 spring 的2绿色框代表 spring 的3黄色框代表枚举的第1个 case剩余7个字节为空。后续的7个字节是为了内存对齐而补齐的内存。
<img src="./../assets/AssociatedEnumMemoryLayoutDemo1.png" style="zoom:25%">
其内存信息如下8字节为1组对应上图
@@ -106,79 +121,88 @@ print("over")
这段内存信息怎么看?我划分了下
```
关联值 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
```shell
case spring 的关联值的第1个 Int 01 00 00 00 00 00 00 00
case spring 的关联值的第2个 Int 02 00 00 00 00 00 00 00
case spring 的关联值的第3个 Int 03 00 00 00 00 00 00 00
表明哪个 case 的索引值: 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个字节为空。
- `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%">
<img src="./../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
case summer 的关联值的第1个 Int 04 00 00 00 00 00 00 00
case summer 的关联值的第2个 Int 05 00 00 00 00 00 00 00
空(按照最大内存预留的位置): 00 00 00 00 00 00 00 00
表明哪个 case 的索引值: 01
内存对齐占用: 00 00 00 00 00 00 00
```
- `season = Season.antumn(6) 带有关联值的枚举,`. `antumn` 1个 Int单个 Int 占8个字节空间所以红色框代表 antumn 的6蓝色框为空绿色框为空黄色框代表枚举的第3个 case剩余7个字节为空。
- `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%">
<img src="./../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
case autumn 的关联值的第1个 Int 06 00 00 00 00 00 00 00
空(按照最大内存预留的位置): 00 00 00 00 00 00 00 00
空(按照最大内存预留的位置): 00 00 00 00 00 00 00 00
表明哪个 case 的索引值: 02
内存对齐占用: 00 00 00 00 00 00 00
```
- `season = Season.winter(true)` 带有关联值的枚举,`. `winter` 1个 Bool单个 Int 占1个字节空间所以红色框代表 winter 的 true蓝色框为空绿色框为空黄色框代表枚举的第4个 case剩余7个字节为空。
- `season = Season.winter(true)` 带有关联值的枚举,`. `winter` 关联值是 1个 Bool单个 Bool 占1个字节空间所以红色框代表 winter 的 true蓝色框为空绿色框为空黄色框代表枚举的第4个 case剩余7个字节为空。
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/AssociatedEnumMemoryLayoutDemo5.png" style="zoom:25%">
<img src="./../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
case winter 的关联值的第1个 Int 01 00 00 00 00 00 00 00
空(按照最大内存预留的位置): 00 00 00 00 00 00 00 00
空(按照最大内存预留的位置): 00 00 00 00 00 00 00 00
表明哪个 case 的索引值: 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%">
<img src="./../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
case unknown 关联值的第1个Int: 00 00 00 00 00 00 00 00
空(按照最大内存预留的位置): 00 00 00 00 00 00 00 00
空(按照最大内存预留的位置): 00 00 00 00 00 00 00 00
表明哪个 case 的索引值: 04
内存对齐占用: 00 00 00 00 00 00 00
```
- `MemoryLayout<Season>.size` 3个 Int 最大为3*81个字节用来表达位置信息`3*8 + 1 = 25`
- `MemoryLayout<Season>.stride` :获取系统分配给数据类型的内存大小,也就是实际内存大小(对齐后的)
- `MemoryLayout<Season>.alignment` 内存对齐系数以8 Byte 为单位,对象分配的内存必须是该值的整数倍
结论:
- **对于有不同枚举值有不同关联结构的枚举,其内存由关联值最大的 case 的内存决定(其余的 case 沿用该结构。所有 case 共享同一块内存区域。该区域大小为最大关联值所需内存)**
- **除了最大内存的 case 外还需要1个字节存储所属哪个 case**(通常 1 字节,具体取决于 case 数量1字节能表示的 case 数量为256个
- **枚举总内存实际大小 = 1个字节存储所属哪个 case + 关联值所占内存最大的 case 的内存大小**
- **另外枚举所占内存还需要考虑内存对齐的情况。比如本例中实际内存为25内存对齐为8字节所以最终分配了32字节的内存**
### 只有一个 case 的枚举
## 只有一个 case 的枚举
```swift
enum SimpleEnum {
@@ -205,7 +229,9 @@ print(MemoryLayout<SimpleEnum>.alignment) // 1
现在好理解2个 case 需要存储1个 Byte 的值来区分是哪个 case1 Byte 可以代表最多256个 case
### 只有1个 case 且带关联值的枚举
## 只有1个 case 且带关联值的枚举
```swift
enum SimpleEnum {
@@ -217,7 +243,10 @@ print(MemoryLayout<SimpleEnum>.stride) // 8
print(MemoryLayout<SimpleEnum>.alignment) // 8
```
带有关联值且只有1个 case 的枚举因为有1个 Int 的关联值,但只有1个 case所以只需要8 Byte 存储关联值即可。
- 带有关联值且只有1个 case 的枚举因为有1个 Int 的关联值需要8 Byte 存储关联值
- 但只有1个 case不需要额外空间来判断所属哪个枚举值所以不需要额外空间
请看下面的对照实验
@@ -232,13 +261,55 @@ print(MemoryLayout<SimpleEnum>.stride) // 16
print(MemoryLayout<SimpleEnum>.alignment) // 8
```
2个 case其中一个 case 有关联值 Int所以需要8 Byte 存 Int 值1 Byte 区分是哪个 case实际需要占用 8 + 1 = 9 Byte内存对齐单位是89向上为16.
2个 case其中一个 case 有关联值 Int所以需要8 Byte 存 Int 值1 Byte 区分是哪个 case实际需要占用 8 + 1 = 9 Byte内存对齐单位是89向上为16
### 用汇编验证下内存
### 为什么不能复用关联值的空间
```
case1 占用8个字节case2 占用1个字节能用 case1 的8个字节的最开始的位置存储 case2 的信息吗?这样的话节省内存
- **内存布局的确定性**Swift 要求枚举实例的内存布局在编译时确定。若允许不同 case 复用同一块内存,会导致运行时动态解析内存布局,降低性能和安全性。
- **所有 case 共享同一块内存**:枚举实例的内存大小由**最大关联值的大小 + 标签所需空间**决定。
- **标签Discriminant**:用于区分不同 case通常占用 1 字节(但具体由 case 数量决定)。
**标签的占用大小**由枚举的 `case` 数量决定:
- **1 个 `case`**:不需要标签(因为没有其他可能性)。
- **2~256 个 `case`**:标签通常占用 **1 字节**`UInt8`,可以表示 256 种可能)。
- **257~65536 个 `case`**:标签占用 **2 字节**`UInt16`)。
- 更大数量依此类推(但实际中几乎不会用到如此多的 `case`)。
- **内存对齐约束**:即使标签的逻辑占用小于 1 字节(例如只有 2 个 `case`,理论上只需 1 位),实际仍会占用 **至少 1 字节**(因为内存按字节寻址)。
- **内存对齐**:总大小会按对齐要求(如 8 字节)向上取整到最近的倍数(即 `stride`)。
以示例中的 `SimpleEnum` 为例:
- `case one(Int)`:需要 8 字节存储 `Int` + 1 字节标签 → 共 9 字节。
- `case two`:仅需 1 字节标签 → 但内存仍需按最大 case 分配(即 8 字节关联值空间 + 1 字节标签 → 总 9 字节)。
因此,无论当前是哪个 case枚举实例始终占用 **9 字节**(对齐后 `stride` 为 16 字节)。
更改验证标签对于枚举占用内存大小的影响
<img src="./../assets/SwiftEnumCaseNumAffectMemory.png" style="zoom:40%" />
可以看到 enum 只有1个 case 的时候内存大小只和最大关联值大小有关1个 case 的情况下不需要额外的空间来判断所属哪个 case。
因此此时枚举的内存大小 = 最大关联值的内存大小 = 8
## 用汇编验证下内存
```swift
enum Season {
case spring(Int, Int, Int)
case summer(Int, Int)
@@ -258,7 +329,7 @@ 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%">
<img src="./../assets/AssociatedEnumMemoryLayoutExplore.png" style="zoom:25%">
将断点处的汇编单独摘出来研究
@@ -294,4 +365,78 @@ print("over")
- n个字节用来存储关联值n取占用内存最大的关联值任何一个 case 的关联值都共用这 n 个字节
- 且存在内存对齐,所以占用大小为 n 和 1 的最大值,再结合内存对齐。
- 如果枚举的定义非常简单系统会用1个字节来存放值最大范围是256个 case。
- 枚举定义如果有原始值,也不会影响内存布局。
- 枚举定义如果有原始值,也不会影响内存布局。
## switch 的工作原理
```swift
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)
switch season {
case let .spring(v1, v2, v3):
print(".spring", v1, v2, v3)
case let .summer(v1, v2):
print(".summer", v1, v2)
case let .antumn(v1):
print(".antumn", v1)
case let .winter(v1):
print(".winter", v1)
case .unknown:
print(".unkown")
}
```
- Swift 先判断 season 的成员值,判断属于哪个 case。
- 如果发现成员值为0则走第1个 case将 season 的前24个字节分别赋值给第1个 case 的 v1、v2、v3
- 如果发现成员值为1则走第2个 case将 season 的前16个字节分别赋值给第2个 case 的 v1、v2
- 如果发现成员值为2则走第3个 case将 season 的前8个字节赋值给第3个 case 的 v1
- 如果发现成员值为3则走第4个 case将 season 的第1个字节赋值给第4个 case 的 v1
- 如果发现成员值为4则走第5个 case则执行第5个 case 的打印逻辑
## Swift 枚举的本质
从表象来看,枚举存在以下情况:
- **不带关联值、不带原始值的基础枚举只占1个字节大小空间且值为默认值**
- **只带有原始值的枚举同样只占用1个字节该字节的值为枚举的位置索引比如1、2而非原始值**
- **带有关联值的枚举内存大小:**
- **对于有不同枚举值有不同关联结构的枚举,其内存由关联值最大的 case 的内存决定(其余的 case 沿用该结构。所有 case 共享同一块内存区域。该区域大小为最大关联值所需内存)**
- **除了最大内存的 case 外还需要1个字节存储所属哪个 case**(通常 1 字节,具体取决于 case 数量1字节能表示的 case 数量为256个
- **枚举总内存实际大小 = 1个字节存储所属哪个 case + 关联值所占内存最大的 case 的内存大小**
- **另外枚举所占内存还需要考虑内存对齐的情况。比如本例中实际内存为25内存对齐为8字节所以最终分配了32字节的内存**
但从本质来讲:
- 对于无关联值、无原始值的简单枚举Swift 编译器会进行内存优化:
- 当枚举 case ≤ 256 个时,使用 1 字节UInt8
- 当 case ≤ 65536 时,使用 2 字节UInt16
- **内存布局的确定性**Swift 要求枚举实例的内存布局在编译时确定。若允许不同 case 复用同一块内存,会导致运行时动态解析内存布局,降低性能和安全性。
- **所有 case 共享同一块内存**:枚举实例的内存大小由**最大关联值的大小 + 标签所需空间**决定。
- **标签Discriminant**:用于区分不同 case通常占用 1 字节(但具体由 case 数量决定)。
**标签的占用大小**由枚举的 `case` 数量决定:
- **1 个 `case`**:不需要标签(因为没有其他可能性)。
- **2~256 个 `case`**:标签通常占用 **1 字节**`UInt8`,可以表示 256 种可能)。
- **257~65536 个 `case`**:标签占用 **2 字节**`UInt16`)。
- 更大数量依此类推(但实际中几乎不会用到如此多的 `case`)。
- **内存对齐约束**:即使标签的逻辑占用小于 1 字节(例如只有 2 个 `case`,理论上只需 1 位),实际仍会占用 **至少 1 字节**(因为内存按字节寻址)。
- **内存对齐**:总大小会按对齐要求(如 8 字节)向上取整到最近的倍数(即 `stride`)。