分析go中slice的奇怪現象

常九發表於2018-10-15

問題描述

片段一:

s := []byte("")
s1 := append(s,'a')
s2 := append(s,'b')
fmt.Println(s1,"=====",s2) // [97] ===== [98]
fmt.Println(string(s1),"======",string(s2)) // a ====== b
複製程式碼

片段二:

s := []byte("")
s1 := append(s,'a')
s2 := append(s,'b')
fmt.Println(string(s1),"======",string(s2)) // b ====== b
複製程式碼

可以看到,片段一和片段二中s1和s2輸出不一致。

問題分析

初看起來,感覺是fmt.Println(s1,"=====",s2)這句話導致了結果的不一樣。

具體原因,且看下面分解。

對於片段二,結果都是b,這個似乎是因為append的時候,賦值給了一個新的變數,導致了s指向的底層資料雖然改變了,但是s記錄的len還是不變的,所以第二次append的時候,就把第一次的值給覆蓋了。所以才得出了都是b的結果。

對於問題二的解釋,其實還少考慮了一個問題,那就是如果在append的時候,底層陣列重新分配了,那麼,就不會出現這個問題了。不信,看下面這個例子:

s := []int{5, 7, 9}
fmt.Printf("%d, %d, %p\n", len(s), cap(s), &s[0]) // 3, 3, 0xc000016240
x := append(s, 11)
fmt.Printf("%d, %d, %p\n", len(x), cap(x), &x[0]) // 4, 6, 0xc0000141e0
y := append(s, 12)
fmt.Printf("%d, %d, %p\n", len(y), cap(y), &y[0]) // 4, 6, 0xc000014210
fmt.Println(s, x, y) // [5 7 9] [5 7 9 11] [5 7 9 12]
複製程式碼

從上面例子可以看到,一開始s的len是3, cap也是3,在第一次append的時候,底層陣列需要擴容,翻倍為6,所以x的len是4, cap是6,此時指向的底層陣列地址已經不同了,第二個append同理。所以兩次append,因為底層資料擴容的原因,兩次append作用的地方是不同的,導致第二次沒有覆蓋第一次的資料。

通過上面的解釋,我們知道了第一個片段出現的原因就是因為兩次append都擴容了,所以沒有出現覆蓋的現象。而第二個片段沒有出現擴容的情況,所以就出現了覆蓋的情況。

那麼為什麼會這樣呢,我們嘗試列印一下,初始化s的cap,可以發現片段一中初始化s的cap為0,片段二為32。具體原因可以看下面的逃逸分析

逃逸分析

使用下面的命令進行分析。

go tool compile -m main.go
複製程式碼

片段一:

main.go:9:13: s1 escapes to heap
main.go:6:13: ([]byte)("") escapes to heap
main.go:9:17: "=====" escapes to heap
main.go:9:17: s2 escapes to heap
main.go:10:20: string(s1) escapes to heap
main.go:10:20: string(s1) escapes to heap
main.go:10:25: "======" escapes to heap
main.go:10:40: string(s2) escapes to heap
main.go:10:40: string(s2) escapes to heap
main.go:9:13: main ... argument does not escape
main.go:10:13: main ... argument does not escape
複製程式碼

可以看到s1s2由於Println,逃逸到了heap上,所以s一開始的cap是0.

片段二:

main.go:9:20: string(s1) escapes to heap
main.go:9:20: string(s1) escapes to heap
main.go:9:25: "======" escapes to heap
main.go:9:40: string(s2) escapes to heap
main.go:9:40: string(s2) escapes to heap
main.go:6:13: main ([]byte)("") does not escape
main.go:9:13: main ... argument does not escape
複製程式碼

可以看到s1s2沒有逃逸,還是在棧上,所以s一開始的cap是32(預設).

相關文章