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

标签 swift enums size memory-layout

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

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

enum Size {
    case small
    case medium
    case large
}

第二种情况,如果一个枚举有关联的值,它的行为就像一个联合。在这种情况下,枚举大小是标签的大小加上最大关联值的大小。在下面的例子中,大小是 1 字节 + 16 字节(字符串)所以 17 字节,这也可以用 MemoryLayout 验证.

enum Value {
    case int(Int)
    case double(Double)
    case string(String)
    case bool(Bool)
}

最后一种情况,因为 Swift 是一种安全的语言,使用标准的非不安全 Swift 代码引用总是有效的,即总是指向内存中的值。这允许编译器在 T 时优化此类枚举。是引用类型:
enum Opt<T> {
    case none
    case some(T)
}

这里是类型 T 的实例不能通过 nil (NULL) 所以编译器对 none 使用这个特殊值案例,因此 OptT 时大小为 8 个字节而不是 9 个字节是引用类型。此优化在 this 中提出所以关于 Rust 的问题,我认为与 Swift 的枚举有相同的行为。

例如,使用这个简单的引用类型,MemoryLayout返回 8 个字节的大小:
class Person {
    var name: String

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

let p = Opt.some(Person(name: "Bob"))  // 8 bytes



我无法弄清楚的是这个枚举的大小(仍然是 T 是引用类型时):
enum Opt<T> {
    case none
    case secondNone
    case some(T)
}

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

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

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

最佳答案

来自 Type Layout: Single-Payload Enums :

If the data type's binary representation has extra inhabitants, that is, bit patterns with the size and alignment of the type but which do not form valid values of that type, they are used to represent the no-data cases, with extra inhabitants in order of ascending numeric value matching no-data cases in declaration order.



您的更多案例示例:
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

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

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

关于关联值是引用类型时的 Swift 枚举大小,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62062590/

相关文章:

java - Spring MVC - 将枚举填充到下拉列表

c# - 使用枚举实现层次结构的最佳 C# 模式是什么?

internet-explorer - SVG 在 Internet Explorer 9 中绘制 Canvas 边界外

c++ - 如何确定 C++ 中对象的大小?

swift - 如何检查一个对象是否是一个集合? ( swift )

java - 为什么,当我在 switch 语句中为每个枚举常量设置 case 时,我仍必须提供默认值?

arrays - 将 Firestore 数据加载到 TableView Swift 4

c++ - 限制数组类型的大小,同时还没有实例

swift - CABasicAnimation 属性列表

arrays - 快速排序 NSDates 数组