Swift 关联值是引用类型时的枚举大小
Swift enum size when associated value is a reference type
我在 Swift 中阅读了有关枚举大小的文档,这是我的理解:
这个简单的只有一个'tag'来区分大小写,默认是一个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
的情况,因此 Opt
的大小当 T
是引用类型时,8 个字节而不是 9 个字节。这种优化是在 中提出的,所以关于 Rust 的问题我认为具有相同的行为 Swift 关于枚举。
例如对于这个简单的引用类型,MemoryLayout
returns 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
没有 'second' 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 中阅读了有关枚举大小的文档,这是我的理解:
这个简单的只有一个'tag'来区分大小写,默认是一个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
的情况,因此 Opt
的大小当 T
是引用类型时,8 个字节而不是 9 个字节。这种优化是在
例如对于这个简单的引用类型,MemoryLayout
returns 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
没有 'second' 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 编译器源代码。