7-7.【高级特性】enum 带关联值时,底层如何存储 discriminant 与 payload?

3 阅读2分钟

一、先给一个抽象模型(便于理解)

对一个带关联值的 enum:

enum E {
    case a(Int8)
    case b(Int64)
}

逻辑模型可以理解为:

[payload (max size)] + [discriminant]
  • payload:能容纳“最大 case”的那块内存
  • discriminant / tag:当前是哪一个 case

⚠️ 这是“概念模型”,不是最终物理布局。


二、payload 是如何确定的?

1️⃣ 选最大的 case

enum E {
    case small(Int8)   // 1 byte
    case big(Int64)    // 8 bytes
}
  • payload size = max(1, 8) = 8
  • payload alignment = max(1, 8) = 8

👉 enum 的 alignment 由最大 payload 决定


三、discriminant(tag)如何存?

1️⃣ 第一优先级:塞进 payload 的空位

这是 Swift enum 的核心优化。

示例 1:Int64 payload

enum E {
    case a(Int64)
    case b(Int64)
}
  • Int64 实际只用 64 bit
  • 某些 bit 可被当作 tag(例如高位)
  • 👉 tag 不需要额外空间
  • size = 8

这种叫:

“spare bits optimization”


2️⃣ 第二优先级:使用 unused bit patterns

enum E {
    case none
    case some(Int)
}
  • Int 并不会用完所有 bit pattern
  • Swift 用“非法 / 未用值”表示 .none
  • 👉 Optional<Int>Int 大小完全一样

这是 enum 布局里最经典的例子


3️⃣ 最坏情况:额外 tag 字节

当:

  • payload 没有 spare bits
  • case 数量较多
enum E {
    case a(Int8)
    case b(Int16)
    case c(Int32)
    case d(Int64)
}
  • payload = Int64 (8 bytes)

  • tag 无法完全塞进去

  • 👉 编译器会:

    • 追加 1~N 字节作为 discriminant
    • 再对齐整个 enum

结果可能是:

[payload (8)] [tag (1)] [padding]

四、真实内存布局示意

例子:需要额外 tag

offset 0~7   payload
offset 8     discriminant
offset 9~15  padding (alignment)
  • size = 16
  • alignment = 8
  • stride = 16

五、enum + 引用类型 payload 的特殊点

enum E {
    case value(String)
    case none
}
  • String 是 COW 值类型
  • 内部含 堆引用
  • payload 是一个 String 的“外壳”
  • discriminant 通常塞进 String 的 spare bits

👉 enum 本身 不拥有额外堆内存


六、与 ABI 稳定性的关系(高阶点)

  • Swift 5 之后 ABI 稳定

  • 标准库 enum(如 Optional)布局稳定

  • 用户自定义 enum

    • 编译器仍有一定自由度
    • 但同一模块内布局是确定的

面试官如果追问,你可以说:

enum 的逻辑模型稳定,但物理布局允许编译器优化。


七、面试必背总结(强烈建议)

带关联值的 enum 使用“最大 payload + discriminant”模型;
discriminant 会优先利用 payload 的 spare bits 或非法 bit pattern;
只有在无法编码时才额外分配 tag 字节,并整体按最大 payload 对齐。