GoのSliceについて
Goのsliceは可変長配列として扱えるデータ構造であり、内部に配列へのポインタを持っていて、その配列についてのlengthとcapacityを保持している。 lengthは配列の有効サイズを示し、capacityはその配列がメモリ確保した最大サイズを示す。 capacity内であれば配列の要素は変更可能であり、メモリが新たに割当てられることはない。 capacity以上のサイズが必要になった場合は、配列にはcapacityの倍のサイズの新たなメモリが割当てられる。
以下のようにsliceそのもののアドレス&s
と、sliceが保持する配列のポインタアドレス&s[0]
を表示してみる。
sliceが新たに作られても、sliceが保持する配列のポインタアドレスは、capacityを超えない限り変わっていないことが分かる。
また、Slicingした際には元の(capacityが拡張されていない)sliceが保持する配列のポインタアドレスを参照しており、capacityを継承していることが分かる。
$ cat sliceTest.go package main import ( "fmt" ) func justAppend(s []int) { s = append(s, 4) fmt.Println("In justAppend") fmt.Println(s) fmt.Println(s[:cap(s)]) fmt.Printf("&s=%p,&s[0]=%p,%d,%d\n\n", &s, &s[0], len(s), cap(s)) } func appendThenReturn(ps *[]int) []int { s := *ps s = append(s, 4) fmt.Println("In appendThenReturn") fmt.Println(s) fmt.Println(s[:cap(s)]) fmt.Printf("&s=%p,&s[0]=%p,%d,%d\n\n", &s, &s[0], len(s), cap(s)) return s } func appendMoreThanCapThenReturn(s []int) []int { s = append(s, 5) s = append(s, 6) fmt.Println("In appendMoreThanCapThenReturn") fmt.Println(s) fmt.Println(s[:cap(s)]) fmt.Printf("&s=%p,&s[0]=%p,%d,%d\n\n", &s, &s[0], len(s), cap(s)) return s } func main() { s := make([]int, 0, 5) s = append(s, 1) s = append(s, 2) s = append(s, 3) fmt.Println("Before justAppend") fmt.Println(s) fmt.Println(s[:cap(s)]) fmt.Printf("&s=%p,&s[0]=%p,%d,%d\n\n", &s, &s[0], len(s), cap(s)) justAppend(s) fmt.Println("After justAppend") fmt.Println(s) fmt.Println(s[:cap(s)]) fmt.Printf("&s=%p,&s[0]=%p,%d,%d\n\n", &s, &s[0], len(s), cap(s)) s = appendThenReturn(&s) fmt.Println("After appendThenReturn") fmt.Println(s) fmt.Println(s[:cap(s)]) fmt.Printf("&s=%p,&s[0]=%p,%d,%d\n\n", &s, &s[0], len(s), cap(s)) t := appendMoreThanCapThenReturn(s) fmt.Println("After appendMoreThanCapThenReturn") fmt.Println(t) fmt.Println(t[:cap(t)]) fmt.Printf("&t=%p,&t[0]=%p,%d,%d\n\n", &t, &t[0], len(t), cap(t)) u := s[0:3] fmt.Println("Show u") fmt.Println(u) fmt.Println(u[:cap(u)]) fmt.Printf("&u=%p,&u[0]=%p,%d,%d\n\n", &u, &u[0], len(u), cap(u)) }
$ go run sliceTest.go Before justAppend [1 2 3] [1 2 3 0 0] &s=0x8201e8140,&s[0]=0x8201f21e0,3,5 In justAppend [1 2 3 4] [1 2 3 4 0] &s=0x8201e81c0,&s[0]=0x8201f21e0,4,5 After justAppend [1 2 3] [1 2 3 4 0] &s=0x8201e8140,&s[0]=0x8201f21e0,3,5 In appendThenReturn [1 2 3 4] [1 2 3 4 0] &s=0x8201e8260,&s[0]=0x8201f21e0,4,5 After appendThenReturn [1 2 3 4] [1 2 3 4 0] &s=0x8201e8140,&s[0]=0x8201f21e0,4,5 In appendMoreThanCapThenReturn [1 2 3 4 5 6] [1 2 3 4 5 6 0 0 0 0] &s=0x8201e8320,&s[0]=0x8201ec190,6,10 After appendMoreThanCapThenReturn [1 2 3 4 5 6] [1 2 3 4 5 6 0 0 0 0] &t=0x8201e8300,&t[0]=0x8201ec190,6,10 Show u [1 2 3] [1 2 3 4 5] &u=0x8201e83c0,&u[0]=0x8201f21e0,3,5
Thanks to
Go のスライスでハマッたところ - Block Rockin’ Codes
Goでxxxのポインタを取っているプログラムはだいたい全部間違っている - Qiita
Go のスライスの内部実装 - Block Rockin’ Codes
↑GCのついての考察は興味深い。