当关联值是引用类型时,Swift 枚举大小

Lou*_*Lac 8 size enums memory-layout swift

我阅读了有关 Swift 中枚举大小的文档,这是我的理解:

这个简单的仅包含一个“标签”来区分情况,默认情况下是一个UInt8值,即small = 0medium = 1等等。所以Size的大小为 1 个字节,可以用 来验证MemoryLayout<Size>.size。我还注意到,如果枚举的情况超过 255 个,显然标记大小会升级为 2 个字节。

enum Size {
    case small
    case medium
    case large
}
Run Code Online (Sandbox Code Playgroud)

第二种情况,如果枚举具有关联值,则其行为类似于联合。在这种情况下,枚举大小是标记的大小加上最大关联值的大小。在以下示例中,大小为 1 字节 + 16 字节(字符串),即 17 字节,也可以使用 进行验证MemoryLayout

enum Size {
    case small
    case medium
    case large
}
Run Code Online (Sandbox Code Playgroud)

最后一种情况,由于 Swift 是一种安全语言,使用标准非不安全 Swift 代码的引用始终有效,即始终指向内存中的值。T这允许编译器在是引用类型时优化此类枚举:

enum Opt<T> {
    case none
    case some(T)
}
Run Code Online (Sandbox Code Playgroud)

这里类型的实例T不能为nil(NULL),因此编译器在这种none情况下使用这个特殊值,因此当它是引用类型Opt时,其大小为 8 字节而不是 9 字节。T这种优化是在关于 Rust 的 SO 问题中提出的我相信它在枚举方面与 Swift 具有相同的行为。

例如,对于这个简单的引用类型,MemoryLayout返回 8 个字节的大小:

class Person {
    var name: String

    init(name: String) {
        self.name = name
    }
}

let p = Opt.some(Person(name: "Bob"))  // 8 bytes
Run Code Online (Sandbox Code Playgroud)

问题

我无法弄清楚这个枚举的大小(仍然是当 T 是引用类型时):

enum Opt<T> {
    case none
    case secondNone
    case some(T)
}
Run Code Online (Sandbox Code Playgroud)

根据 ,为什么这个也是 8 字节MemoryLayout

按照我的理解,它应该是9个字节。NULL 优化是唯一可能的,因为none可以用 NULL 表示,但在我的示例中没有“第二个”NULL 值secondNone,因此这里应该需要一个标签来区分情况。

编译器是否会因此自动将此枚举转换为引用类型(类似于枚举indirect)?这可以解释 8 字节的大小。我如何验证最后一个假设?

Mar*_*n R 3

类型布局:单有效负载枚举

如果数据类型的二进制表示具有额外的居民,即具有该类型的大小和对齐方式的位模式,但不形成该类型的有效值,则它们用于表示无数据情况,并且额外的居民按顺序排列与声明顺序中的无数据案例匹配的升序数值。

您的示例有更多案例:

enum Opt<T> {
    case a, b, c, d, e, f, g, h, i, j, k
    case l, m, n, o, p, q, r, s, t, u, v
    case some(T)
}

class Person {
    var name: String
    init(name: String) { self.name = name }
}

print(unsafeBitCast(Opt<Person>.a, to: UnsafeRawPointer.self))
// 0x0000000000000000

print(unsafeBitCast(Opt<Person>.b, to: UnsafeRawPointer.self))
// 0x0000000000000002

print(unsafeBitCast(Opt<Person>.v, to: UnsafeRawPointer.self))
// 0x000000000000002a

let p = Person(name: "Bob")
print(unsafeBitCast(Opt.some(p), to: UnsafeRawPointer.self))
// 0x00006030000435d0
Run Code Online (Sandbox Code Playgroud)

显然,0x00x2、 ...0x2a是指针的无效位模式,因此用于其他情况。

精确的算法似乎没有文档记录,可能需要检查 Swift 编译器源代码。