この記事について
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 は今回試した際に確保された配列のアドレス)
なお、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
は次のような構造になる。
スライシングのポイントは以下。
- 配列自体はコピーされない
- スライス
a
,b
は同じ配列を参照する- ポインター、Len、Cap が異なってるだけ
- なので
a
のデータを変更するとb
にも影響がある
一応、スライス a
, b
を1つの図にするとこんな感じ。
var で定義したスライス
以下のように宣言すると、Pointer = 0, Len = 0, Cap = 0 というスライスになる。つまりこの時点ではスライス変数は存在しても、配列は存在していない。
var c []byte
こういう状態。
このスライスに対して append() 関数を呼んで要素を追加することで配列が確保される。また、必要に応じて配列が拡張される。
for i :=0; i < 100; i++ {
c = append(c, byte(i))
}
上記コードを実行した後は、例えば以下のようになる。
(今回試した際は、Len=100、Cap=128になった)。
スライス var c []byte
と c := []byte{}
の違い
var c []byte
は先に見たように配列を指してないが、c := []byte{}
は Len, Cap ともに 0 であるものの、どこかのアドレスを指している(配列のメモリが確保されている?)。
これでどんな違いが出るかというと、例えば 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