LoginSignup
47
27

More than 5 years have passed since last update.

Swiftのenumのメモリレイアウトの最適化が凄い

Posted at

導入

この記事はこの記事の続編です(Swiftのメモリレイアウトを調べる)。前回の実験で、クラスの入れ子Optionalにおいて、値がnoneになる場合、ポインタ上にエンコードした整数値として値が表現されており、メモリサイズがポインタ分より増えないという最適化がされている事がわかりました。これは言語組み込みのOptionalならではの仕組みなのでしょうか。swiftにおいてOptionalはenumとして表現されています。では自作enumではどうなるのでしょうか。調べてみました。(Swift 3.0.2)

自作Optional

自作のenumを定義して同じことを調べてみます。

enum EA<T> {
    case c0(T)
    case c1
}

func test1() {
    print("--: test1")

    print("----: .c0(SA())")
    var x1: EA<SA> = .c0(SA())
    let px1 = UnsafeRawPointer(UnsafeMutablePointer(&x1))
    dump(px1, MemoryLayout<EA<SA>>.size)

    print("----: .c0(.c0(.c0(.c0(SA()))))")
    var x2: EA<EA<EA<EA<SA>>>> = .c0(.c0(.c0(.c0(SA()))))
    let px2 = UnsafeRawPointer(UnsafeMutablePointer(&x2))
    dump(px2, MemoryLayout<EA<EA<EA<EA<SA>>>>>.size)

    print("----: .c0(.c0(.c1))")
    var x3: EA<EA<EA<EA<SA>>>> = .c0(.c0(.c1))
    let px3 = UnsafeRawPointer(UnsafeMutablePointer(&x3))
    dump(px3, MemoryLayout<EA<EA<EA<EA<SA>>>>>.size)

    print("----: .c1")
    var x4: EA<EA<EA<EA<SA>>>> = .c1
    let px4 = UnsafeRawPointer(UnsafeMutablePointer(&x4))
    dump(px4, MemoryLayout<EA<EA<EA<EA<SA>>>>>.size)

    print("----: .c0(CA())")
    var x5: EA<CA> = .c0(CA())
    let px5 = UnsafeRawPointer(UnsafeMutablePointer(&x5))
    dump(px5, MemoryLayout<EA<CA>>.size)
    print("--")
    let ppx5 = px5.bindMemory(to: UnsafeRawPointer.self, capacity: 1).pointee
    dump(ppx5, 32)

    print("----: .c0(.c0(.c0(.c0(CA()))))")
    var x6: EA<EA<EA<EA<CA>>>> = .c0(.c0(.c0(.c0(CA()))))
    let px6 = UnsafeRawPointer(UnsafeMutablePointer(&x6))
    dump(px6, MemoryLayout<EA<EA<EA<EA<CA>>>>>.size)
    print("--")
    let ppx6 = px6.bindMemory(to: UnsafeRawPointer.self, capacity: 1).pointee
    dump(ppx6, 32)

    print("----: .c0(.c0(.c0(.c1)))")
    var x7: EA<EA<EA<EA<CA>>>> = .c0(.c0(.c0(.c1)))
    let px7 = UnsafeRawPointer(UnsafeMutablePointer(&x7))
    dump(px7, MemoryLayout<EA<EA<EA<EA<CA>>>>>.size)

    print("----: .c0(.c0(.c1))")
    var x8: EA<EA<EA<EA<CA>>>> = .c0(.c0(.c1))
    let px8 = UnsafeRawPointer(UnsafeMutablePointer(&x8))
    dump(px8, MemoryLayout<EA<EA<EA<EA<CA>>>>>.size)

    print("----: .c0(.c1)")
    var x9: EA<EA<EA<EA<CA>>>> = .c0(.c1)
    let px9 = UnsafeRawPointer(UnsafeMutablePointer(&x9))
    dump(px9, MemoryLayout<EA<EA<EA<EA<CA>>>>>.size)

    print("----: .c1")
    var x10: EA<EA<EA<EA<CA>>>> = .c1
    let px10 = UnsafeRawPointer(UnsafeMutablePointer(&x10))
    dump(px10, MemoryLayout<EA<EA<EA<EA<CA>>>>>.size)
}

出力

--: test1
----: .c0(SA())
[0x00007fff541e93c0] d1 c1 b1 a1 d2 c2 b2 a2
[0x00007fff541e93c8] d3 c3 b3 a3 d4 c4 b4 a4
[0x00007fff541e93d0] 00
----: .c0(.c0(.c0(.c0(SA()))))
[0x00007fff541e93a8] d1 c1 b1 a1 d2 c2 b2 a2
[0x00007fff541e93b0] d3 c3 b3 a3 d4 c4 b4 a4
[0x00007fff541e93b8] 00 00 00 00
----: .c0(.c0(.c1))
[0x00007fff541e9390] 00 00 00 00 00 00 00 00
[0x00007fff541e9398] 00 00 00 00 00 00 00 00
[0x00007fff541e93a0] 00 01 00 00
----: .c1
[0x00007fff541e9378] 00 00 00 00 00 00 00 00
[0x00007fff541e9380] 00 00 00 00 00 00 00 00
[0x00007fff541e9388] 00 00 00 01
----: .c0(CA())
[0x00007fff541e9370] 50 96 56 75 8d 7f 00 00
--
[0x00007f8d75569650] 38 70 9b 0f 01 00 00 00
[0x00007f8d75569658] 04 00 00 00 02 00 00 00
[0x00007f8d75569660] d1 c1 b1 a1 d2 c2 b2 a2
[0x00007f8d75569668] d3 c3 b3 a3 d4 c4 b4 a4
----: .c0(.c0(.c0(.c0(CA()))))
[0x00007fff541e9368] 40 0d 7a 75 8d 7f 00 00
--
[0x00007f8d757a0d40] 38 70 9b 0f 01 00 00 00
[0x00007f8d757a0d48] 04 00 00 00 02 00 00 00
[0x00007f8d757a0d50] d1 c1 b1 a1 d2 c2 b2 a2
[0x00007f8d757a0d58] d3 c3 b3 a3 d4 c4 b4 a4
----: .c0(.c0(.c0(.c1)))
[0x00007fff541e9360] 00 00 00 00 00 00 00 00
----: .c0(.c0(.c1))
[0x00007fff541e9358] 02 00 00 00 00 00 00 00
----: .c0(.c1)
[0x00007fff541e9350] 04 00 00 00 00 00 00 00
----: .c1
[0x00007fff541e9348] 06 00 00 00 00 00 00 00

全く同じです!Optionalにかぎらず、自作Optionalでもnoneのケースでは値が2, 4, 6のポインタが現れました。

2パラenum

ところで型パラが2つあったらそもそもメモリレイアウトはどうなるのでしょうか。メモリサイズの違う値 SB, クラス CB を追加した
上でいろいろと試してみます。

struct SB {
    var v1: UInt32 = 0xA5B5C5D5
    var v2: UInt32 = 0xA6B6C6D6
}

class CB {
    var v1: UInt32 = 0xA5B5C5D5
    var v2: UInt32 = 0xA6B6C6D6
}

enum EB<X, Y> {
    case c0(X)
    case c1(Y)
}

これに対して、次のパターンをテストします。

  • 大きさの違う値型2つ
  • 大きさの違うクラス型2つ
  • 値型とクラス型
    print("x1")
    var x1: EB<SA, SB> = .c0(SA())
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x1)),
         MemoryLayout<EB<SA, SB>>.size)

    print("x2")
    var x2: EB<SA, SB> = .c1(SB())
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x2)),
         MemoryLayout<EB<SA, SB>>.size)

    print("x3")
    var x3: EB<CA, CB> = .c0(CA())
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x3)),
         MemoryLayout<EB<CA, CB>>.size)
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x3))
        .bindMemory(to: UnsafeRawPointer.self, capacity: 1).pointee,
         32)

    print("x4")
    var x4: EB<CA, CB> = .c1(CB())
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x4)),
         MemoryLayout<EB<CA, CB>>.size)
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x4))
        .bindMemory(to: UnsafeRawPointer.self, capacity: 1).pointee,
         24)

    print("x5")
    var x5: EB<SA, CA> = .c0(SA())
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x5)),
         MemoryLayout<EB<SA, CB>>.size)

    print("x6")
    var x6: EB<SA, CA> = .c1(CA())
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x6)),
         MemoryLayout<EB<SA, CB>>.size)
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x6))
        .bindMemory(to: UnsafeRawPointer.self, capacity: 1).pointee,
         32)

出力

x1
[0x00007fff541e93c0] d1 c1 b1 a1 d2 c2 b2 a2
[0x00007fff541e93c8] d3 c3 b3 a3 d4 c4 b4 a4
[0x00007fff541e93d0] 00
x2
[0x00007fff541e93a8] d5 c5 b5 a5 d6 c6 b6 a6
[0x00007fff541e93b0] 00 00 00 00 00 00 00 00
[0x00007fff541e93b8] 01
x3
[0x00007fff541e9398] 40 0d 7a 75 8d 7f 00 00
[0x00007fff541e93a0] 00
[0x00007f8d757a0d40] 38 70 9b 0f 01 00 00 00
[0x00007f8d757a0d48] 04 00 00 00 02 00 00 00
[0x00007f8d757a0d50] d1 c1 b1 a1 d2 c2 b2 a2
[0x00007f8d757a0d58] d3 c3 b3 a3 d4 c4 b4 a4
x4
[0x00007fff541e9388] e0 d2 79 75 8d 7f 00 00
[0x00007fff541e9390] 01
[0x00007f8d7579d2e0] d8 72 9b 0f 01 00 00 00
[0x00007f8d7579d2e8] 04 00 00 00 02 00 00 00
[0x00007f8d7579d2f0] d5 c5 b5 a5 d6 c6 b6 a6
x5
[0x00007fff541e9370] d1 c1 b1 a1 d2 c2 b2 a2
[0x00007fff541e9378] d3 c3 b3 a3 d4 c4 b4 a4
[0x00007fff541e9380] 00
x6
[0x00007fff541e9358] 30 08 79 75 8d 7f 00 00
[0x00007fff541e9360] 00 00 00 00 00 00 00 00
[0x00007fff541e9368] 01
[0x00007f8d75790830] 38 70 9b 0f 01 00 00 00
[0x00007f8d75790838] 04 00 00 00 02 00 00 00
[0x00007f8d75790840] d1 c1 b1 a1 d2 c2 b2 a2
[0x00007f8d75790848] d3 c3 b3 a3 d4 c4 b4 a4

値型はその値型の大きさ、クラス型はポインタの大きさとみなした上で、2つの型の大きい方に合わせて、末尾にcaseを決める整数値が付くようです。クラス型2つの場合は、前回予測したポインタ8バイト+種類の1バイトの9バイトの型になります。

では、このような2パラの型の片方に、これまでの1パラenumのクラスを仕込んだらどうなるのでしょうか。片方を値型、片方を入れ子1パラクラスにしてみます。

    print("x7")
    var x7: EB<SA, EA<EA<CA>>> = .c0(SA())
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x7)),
         MemoryLayout<EB<SA, EA<EA<CA>>>>.size)

    print("x8")
    var x8: EB<SA, EA<EA<CA>>> = .c1(.c0(.c0(CA())))
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x8)),
         MemoryLayout<EB<SA, EA<EA<CA>>>>.size)
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x8))
        .bindMemory(to: UnsafeRawPointer.self, capacity: 1).pointee,
         32)

    print("x9")
    var x9: EB<SA, EA<EA<CA>>> = .c1(.c1)
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x9)),
         MemoryLayout<EB<SA, EA<EA<CA>>>>.size)

出力

x7
[0x00007fff541e9340] d1 c1 b1 a1 d2 c2 b2 a2
[0x00007fff541e9348] d3 c3 b3 a3 d4 c4 b4 a4
[0x00007fff541e9350] 00
x8
[0x00007fff541e9328] f0 65 72 75 8d 7f 00 00
[0x00007fff541e9330] 00 00 00 00 00 00 00 00
[0x00007fff541e9338] 01
[0x00007f8d757265f0] 38 70 9b 0f 01 00 00 00
[0x00007f8d757265f8] 04 00 00 00 02 00 00 00
[0x00007f8d75726600] d1 c1 b1 a1 d2 c2 b2 a2
[0x00007f8d75726608] d3 c3 b3 a3 d4 c4 b4 a4
x9
[0x00007fff541e9310] 02 00 00 00 00 00 00 00
[0x00007fff541e9318] 00 00 00 00 00 00 00 00
[0x00007fff541e9320] 01

サイズは値型16バイト+種類の1バイトで17バイトになりました。そしてクラス型の場合は、例の 2のポインタ がその値部分に出現しました。すごい、どうやら部分的な表現としてもこの方式は出現するようです。

自作Optionalと本物Optionalの組み合わせ

ある特定の1パラの型が入れ子になっている場合を特別扱いしているのでしょうか?自作Optionalと本物Optionalを組み合わせて実験してみます。

    print("x1")
    var x1: EA<CA?> = .c0(.some(CA()))
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x1)),
         MemoryLayout<EA<CA?>>.size)
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x1))
        .bindMemory(to: UnsafeRawPointer.self, capacity: 1).pointee,
         32)

    print("x2")
    var x2: EA<CA?> = .c1
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x2)),
         MemoryLayout<EA<CA?>>.size)

出力

x1
[0x00007fff541e93d0] 50 96 56 75 8d 7f 00 00
[0x00007f8d75569650] 38 70 9b 0f 01 00 00 00
[0x00007f8d75569658] 04 00 00 00 02 00 00 00
[0x00007f8d75569660] d1 c1 b1 a1 d2 c2 b2 a2
[0x00007f8d75569668] d3 c3 b3 a3 d4 c4 b4 a4
x2
[0x00007fff541e93c8] 02 00 00 00 00 00 00 00

うわ!出た!違う型同士の組み合わせでもこの特殊ポインタは出現するようです。

case定義順の変更

ケース0に値、というのが条件なのでしょうか?ケース0を無、ケース1を値にしたenumで試してみます。

enum EA2<T> {
    case c0
    case c1(T)
}
    var x1: EA2<CA?> = .c0
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x1)),
         MemoryLayout<EA2<CA?>>.size)

出力

[0x00007fff541e93d0] 02 00 00 00 00 00 00 00

どわ〜〜〜!

無のケースが複数あるenum

1つのケースが値、1つのケースが無の場合に特別扱いしているのでしょうか?無のケースを2種類もった、1パラ3択enumを試してみます。

enum EA3<T> {
    case c0(T)
    case c1
    case c2
}
    print("x1")
    var x1: EA3<CA?> = .c1
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x1)),
         MemoryLayout<EA3<CA?>>.size)

    print("x2")
    var x2: EA3<CA?> = .c2
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x2)),
         MemoryLayout<EA3<CA?>>.size)

出力

x1
[0x00007fff541e93d0] 02 00 00 00 00 00 00 00
x2
[0x00007fff541e93c8] 04 00 00 00 00 00 00 00

これもいけるの!?でも変です、4というのは無の入れ子が2重になった時に出現する値でした。横のパターンで出現するなら、 EA3 をさらに入れ子にすることで衝突するケースが出てくるはずです。試してみましょう。

EA3 には無のパターンが2種類あるので、値の中の無が2種類、根本での無が2種類で、全部で4種類の無があるわけです。

    print("x3")
    var x3: EA3<EA3<CA>> = .c0(.c1)
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x3)),
         MemoryLayout<EA3<CA?>>.size)

    print("x4")
    var x4: EA3<EA3<CA>> = .c0(.c2)
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x4)),
         MemoryLayout<EA3<CA?>>.size)

    print("x5")
    var x5: EA3<EA3<CA>> = .c1
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x5)),
         MemoryLayout<EA3<CA?>>.size)

    print("x6")
    var x6: EA3<EA3<CA>> = .c2
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x6)),
         MemoryLayout<EA3<CA?>>.size)

出力

x3
[0x00007fff541e93c0] 00 00 00 00 00 00 00 00
x4
[0x00007fff541e93b8] 02 00 00 00 00 00 00 00
x5
[0x00007fff541e93b0] 04 00 00 00 00 00 00 00
x6
[0x00007fff541e93a8] 06 00 00 00 00 00 00 00

それぞれ違う値になりました!マジか・・・。どうやら見えてきました。ケースの分岐パターンを列挙して、無になるパターンに連番を付けていると思われます。つまり、 値を持つ場所が1つだけ になっているのが条件ではないでしょうか。試してみます。

2パラだが値が1通りのパターン

まず、値を持たないenumを追加します。

enum EA4 {
    case c0
    case c1
    case c2
}

そして、以下の型を考えます。

    var x1: EB<EA3, CA>

これなら、 EB の1番目に入っても無になるので、値があるのは2番目の場合だけです。

    typealias ET = EB<EA4, CA>

    print("x1")
    var x1: ET = .c0(.c0)
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x1)),
         MemoryLayout<ET>.size)

    print("x2")
    var x2: ET = .c0(.c1)
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x2)),
         MemoryLayout<ET>.size)

    print("x3")
    var x3: ET = .c0(.c2)
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x3)),
         MemoryLayout<ET>.size)

    print("x4")
    var x4: ET = .c1(CA())
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x4)),
         MemoryLayout<ET>.size)
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x4))
        .bindMemory(to: UnsafeRawPointer.self, capacity: 1).pointee,
         32)

出力

x1
[0x00007fff5a6b33c8] 00 00 00 00 00 00 00 00
[0x00007fff5a6b33d0] 00
x2
[0x00007fff5a6b33b8] 01 00 00 00 00 00 00 00
[0x00007fff5a6b33c0] 00
x3
[0x00007fff5a6b33a8] 02 00 00 00 00 00 00 00
[0x00007fff5a6b33b0] 00
x4
[0x00007fff5a6b3398] b0 79 d4 e6 9e 7f 00 00
[0x00007fff5a6b33a0] 01
[0x00007f9ee6d479b0] 38 f0 42 09 01 00 00 00
[0x00007f9ee6d479b8] 04 00 00 00 02 00 00 00
[0x00007f9ee6d479c0] d1 c1 b1 a1 d2 c2 b2 a2
[0x00007f9ee6d479c8] d3 c3 b3 a3 d4 c4 b4 a4

おや、残念。外側の EB が9バイトになるパターンでした。

型パラが1つで無が1通りというのが条件に思われます。

複雑なケース

下記2つの型を作ります。

enum EC1<T> {
    case c0
    case c1(T)
    case c2
    case c3
}

enum EC2<T> {
    case c0
    case c1
    case c2(T)
    case c3
}

これらを組み合わせます。

    typealias ET = EC1<EC2<EC1<EC2<EC1<CA>>>>>

こうするとケースには以下のパターンがあります。

  • EC1[0]
  • EC1[1] / EC2[0]
  • EC1[1] / EC2[1]
  • EC1[1] / EC2[2] / EC1[0]
  • EC1[1] / EC2[2] / EC1[1] / EC2[0]
  • EC1[1] / EC2[2] / EC1[1] / EC2[1]
  • EC1[1] / EC2[2] / EC1[1] / EC2[2] / EC1[0]
  • EC1[1] / EC2[2] / EC1[1] / EC2[2] / EC1[1] / CA (値あり)
  • EC1[1] / EC2[2] / EC1[1] / EC2[2] / EC1[2]
  • EC1[1] / EC2[2] / EC1[1] / EC2[2] / EC1[3]
  • EC1[1] / EC2[2] / EC1[1] / EC2[3]
  • EC1[1] / EC2[2] / EC1[2]
  • EC1[1] / EC2[2] / EC1[3]
  • EC1[1] / EC2[3]
  • EC1[2]
  • EC1[3]

試してみましょう。

    typealias ET = EC1<EC2<EC1<EC2<EC1<CA>>>>>

    var x: ET = .c0

    print("--")
    x = .c0
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x)),
         MemoryLayout<ET>.size)

    print("--")
    x = .c1(.c0)
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x)),
         MemoryLayout<ET>.size)

    print("--")
    x = .c1(.c1)
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x)),
         MemoryLayout<ET>.size)

    print("--")
    x = .c1(.c2(.c0))
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x)),
         MemoryLayout<ET>.size)

    print("--")
    x = .c1(.c2(.c1(.c0)))
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x)),
         MemoryLayout<ET>.size)

    print("--")
    x = .c1(.c2(.c1(.c1)))
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x)),
         MemoryLayout<ET>.size)

    print("--")
    x = .c1(.c2(.c1(.c2(.c0))))
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x)),
         MemoryLayout<ET>.size)

    print("--")
    x = .c1(.c2(.c1(.c2(.c1(CA())))))
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x)),
         MemoryLayout<ET>.size)

    print("--")
    x = .c1(.c2(.c1(.c2(.c2))))
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x)),
         MemoryLayout<ET>.size)

    print("--")
    x = .c1(.c2(.c1(.c2(.c3))))
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x)),
         MemoryLayout<ET>.size)

    print("--")
    x = .c1(.c2(.c1(.c3)))
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x)),
         MemoryLayout<ET>.size)

    print("--")
    x = .c1(.c2(.c2))
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x)),
         MemoryLayout<ET>.size)

    print("--")
    x = .c1(.c2(.c3))
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x)),
         MemoryLayout<ET>.size)

    print("--")
    x = .c1(.c3)
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x)),
         MemoryLayout<ET>.size)

    print("--")
    x = .c2
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x)),
         MemoryLayout<ET>.size)

    print("--")
    x = .c3
    dump(UnsafeRawPointer(UnsafeMutablePointer(&x)),
         MemoryLayout<ET>.size)

出力

--
[0x00007fff5828a3d0] 18 00 00 00 00 00 00 00
--
[0x00007fff5828a3d0] 12 00 00 00 00 00 00 00
--
[0x00007fff5828a3d0] 14 00 00 00 00 00 00 00
--
[0x00007fff5828a3d0] 0c 00 00 00 00 00 00 00
--
[0x00007fff5828a3d0] 06 00 00 00 00 00 00 00
--
[0x00007fff5828a3d0] 08 00 00 00 00 00 00 00
--
[0x00007fff5828a3d0] 00 00 00 00 00 00 00 00
--
[0x00007fff5828a3d0] e0 51 63 f1 d6 7f 00 00
--
[0x00007fff5828a3d0] 02 00 00 00 00 00 00 00
--
[0x00007fff5828a3d0] 04 00 00 00 00 00 00 00
--
[0x00007fff5828a3d0] 0a 00 00 00 00 00 00 00
--
[0x00007fff5828a3d0] 0e 00 00 00 00 00 00 00
--
[0x00007fff5828a3d0] 10 00 00 00 00 00 00 00
--
[0x00007fff5828a3d0] 16 00 00 00 00 00 00 00
--
[0x00007fff5828a3d0] 1a 00 00 00 00 00 00 00
--
[0x00007fff5828a3d0] 1c 00 00 00 00 00 00 00

いけましたね。2から1cまでのポインタが生成されました。ところで、番号の付き方が若干謎です。さっきのリストに割り当ててみます。16進数はわかりにくいので10進数にして、全部2の倍数なのでシフトされていると考えてこれを割ります。

  • 12: EC1[0]
  • 9: EC1[1] / EC2[0]
  • 10: EC1[1] / EC2[1]
  • 6: EC1[1] / EC2[2] / EC1[0]
  • 3: EC1[1] / EC2[2] / EC1[1] / EC2[0]
  • 4: EC1[1] / EC2[2] / EC1[1] / EC2[1]
  • 0: EC1[1] / EC2[2] / EC1[1] / EC2[2] / EC1[0]
  • xx: EC1[1] / EC2[2] / EC1[1] / EC2[2] / EC1[1] / CA (値あり)
  • 1: EC1[1] / EC2[2] / EC1[1] / EC2[2] / EC1[2]
  • 2: EC1[1] / EC2[2] / EC1[1] / EC2[2] / EC1[3]
  • 5: EC1[1] / EC2[2] / EC1[1] / EC2[3]
  • 7: EC1[1] / EC2[2] / EC1[2]
  • 8: EC1[1] / EC2[2] / EC1[3]
  • 11: EC1[1] / EC2[3]
  • 13: EC1[2]
  • 14: EC1[3]

並べなおしてみます。

  • xx: EC1[1] / EC2[2] / EC1[1] / EC2[2] / EC1[1] / CA (値あり)
  • 0: EC1[1] / EC2[2] / EC1[1] / EC2[2] / EC1[0]
  • 1: EC1[1] / EC2[2] / EC1[1] / EC2[2] / EC1[2]
  • 2: EC1[1] / EC2[2] / EC1[1] / EC2[2] / EC1[3]
  • 3: EC1[1] / EC2[2] / EC1[1] / EC2[0]
  • 4: EC1[1] / EC2[2] / EC1[1] / EC2[1]
  • 5: EC1[1] / EC2[2] / EC1[1] / EC2[3]
  • 6: EC1[1] / EC2[2] / EC1[0]
  • 7: EC1[1] / EC2[2] / EC1[2]
  • 8: EC1[1] / EC2[2] / EC1[3]
  • 9: EC1[1] / EC2[0]
  • 10: EC1[1] / EC2[1]
  • 11: EC1[1] / EC2[3]
  • 12: EC1[0]
  • 13: EC1[2]
  • 14: EC1[3]

並び替えると、ネストの深さが綺麗になりました。この形はこれまでのOptionalの入れ子のときとおなじになります。つまり、値を持つことになるケースを優先、値が無いケースを定義が若い順にその後ろに並べる、というソートルールのようですね。

Swiftすごい!!

終わりに

EB のケースができなかったのが気になります。EBのケースでも、片方が値を持たない型で埋まっていれば同じようにできたと思います。今後のアップデートでそうなるかもしれませんね。

ソースはこちら

47
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
47
27