9
1

More than 3 years have passed since last update.

Go言語: スライスの構造

Last updated at Posted at 2020-04-12

この記事について

Go言語のスライスについて、その内部構造を整理します。

まとめ

  • スライスとは、配列をラップして使いやすくした型
    • スライスと、配列の実体はメモリ上では別のオブジェクト
  • スライスは以下の3つのパラメータを持つ構造体
    • 配列の実体へのポインター
    • 配列のうち、そのスライスで使用可能なサイズ (Len)
    • 配列の実際のサイズ (Cap)

  • reflect.SliceHeader を用いてスライスの構造を見ることが出来る
    type SliceHeader struct {
        Data uintptr
        Len  int
        Cap  int
    }

説明

スライスの作成

まずは、スライス(Len = 5, Cap = 8 )を作成してみる。

    a := make([]byte, 5, 8)

こんな構造になる。
(Pointer の値 0xc00000a0d0 は今回試した際に確保された配列のアドレス)

slice0010.png

なお、Capが余っていても Len より大きい要素にはアクセスできない。アクセスすると実行時に panic: runtime error: index out of range [5] with length 5 というパニックが起きる。

     a[0] = 0x10
     a[1] = 0x11
     a[2] = 0x12
     a[3] = 0x13
     a[4] = 0x14
     //a[5] = 0x15 → これは実行時にパニック

スライシング

a をスライシングしてスライス b を生成してみる。

    b := a[1:4]

この場合、スライス b は次のような構造になる。

slice0020.png

スライシングのポイントは以下。

  • 配列自体はコピーされない
  • スライス a, b は同じ配列を参照する
    • ポインター、Len、Cap が異なってるだけ
  • なので a のデータを変更すると b にも影響がある

一応、スライス a, b を1つの図にするとこんな感じ。

slice0025.png

var で定義したスライス

以下のように宣言すると、Pointer = 0, Len = 0, Cap = 0 というスライスになる。つまりこの時点ではスライス変数は存在しても、配列は存在していない。

    var c []byte

こういう状態。

slice0031.png

このスライスに対して append() 関数を呼んで要素を追加することで配列が確保される。また、必要に応じて配列が拡張される。

    for i :=0; i < 100; i++ {
        c = append(c, byte(i))
    }

上記コードを実行した後は、例えば以下のようになる。
(今回試した際は、Len=100、Cap=128になった)。

slice0030.png

スライス var c []bytec := []byte{} の違い

var c []byte は先に見たように配列を指してないが、c := []byte{} は Len, Cap ともに 0 であるものの、どこかのアドレスを指している(配列のメモリが確保されている?)。

slice0040.png

これでどんな違いが出るかというと、例えば json の Encoder を通すと前者は null、後者は空文字として出力される。

    var c1 []byte    // 配列を指してない
    c2 := []byte{}   // どこかのアドレスを指している

    buf1 := new(bytes.Buffer)
    buf2 := new(bytes.Buffer)
    json.NewEncoder(buf1).Encode(&c1)
    json.NewEncoder(buf2).Encode(&c2)
    fmt.Printf("var c1 []byte  ===> %s", buf1.String())
    fmt.Printf("c2 := []byte{} ===> %s", buf2.String())

出力結果:

var c1 []byte  ===> null
c2 := []byte{} ===> ""

スライス変数の中身

スライスは len() と cap() 関数でそれぞれ Length と Capacity を取得できるが、reflect.SliceHeader に変換することで中身を見ることもできる。

reflect.SliceHeader はこんな構造。

type SliceHeader struct {
    Data uintptr  // これが配列のアドレス
    Len  int
    Cap  int
}

以下のように unsafe.Pointer を経由して、スライスを reflect.SliceHeader に変換できる。

    d := make([]byte, 5, 8)
    uptr := unsafe.Pointer(&d)
    hdr := (*reflect.SliceHeader)(uptr)
    fmt.Printf("Data: %x, Len: %d, Cap: %d\n", hdr.Data, hdr.Len, hdr.Cap) 

実行例: Data: c000079ee0, Len: 5, Cap: 8

参考

9
1
1

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
9
1