前言:
當初主要是RUST在官網上提出他們可以避免掉std:vector.push_back造成的referene 問題. 於是我也很好奇,究竟Golang會怎麼樣處理這樣的問題. 也由於Go本身把vector拿掉,我們就用Slice來試試看.
筆記:
Slice 本身的結構包含資料本身,長度(len)與容量(cap),根據Slice Internal Document裡面提到的.而且操作slice的append的時候,會根據你目前的容量(cap)來決定是否要重新分配記憶體位置.
比起std::vector好的是,如果記憶體重新分配了,原先參照的參數不會得到空的記憶體位置,而會變成是複製的內容. 但是要注意的是,參照的變數就不會跟著變動了. 這聽起來就是比較安全一點,不過程式不會造成pointer to null並不代表比較好,因為在這種狀況下.所參照的數值已經被修改,你可能會遇到更不能預測的問題.
所以比較優雅的方式,還是事先要保留足夠的資料.避免append造成的資料重新建立.
奇怪的狀況,cap增長的大小根據compiler的不同
我本來是很清楚這樣的狀況,直到我把我以下的程式跑在本地端(Mac/Win)還有遠端的Golang Playground發現結果不一樣了.
是的! 靚過第一次slice append之後,cap(slice)增長的數值竟然不一樣?? (Playground是2,本地端是1) 一樣的狀況.類似的操作在c++ std::vector 就沒看到 (Win/Mac/Coliru online compiler) 增加過後的的cap都維持為”1”. 這裏有參考的C++程式碼.
於是我在Stackoverflow詢問了這樣的問題,也引起了一些人的注意,幫我把問題發到了Golang上面.雖然沒有辦法說服大家把這樣類似的問題放入spec或是warning,但是真的提醒大家,真正在使用slice的時候,必須像使用std::vector一樣的小心.最好的方式是不要參照,如果真的要參照的話,也建議使用保留的slice大小來避免記憶體重新的分配所造成的參照失效.
以下是完整的Golang 測試程式碼
package main import "fmt" func main() { var a []int var b []int fmt.Printf("a len=%d, cap=%d \n", len(a), cap(a)) a = append(a, 0) b = append(b, 0) p := &a[0] fmt.Printf("a[0] = %d pointer=%d, len=%d, cap=%d, p = %d \n", a[0], &a[0], len(a), cap(a), *p) a[0] = 2 fmt.Printf("a[0] = %d pointer=%d, len=%d, cap=%d, p = %d \n", a[0], &a[0], len(a), cap(a), *p) /* a[0] = 0, p = 0 a[0] = 2, p = 2 */ var c []int var d []int fmt.Printf("c len=%d, cap=%d \n", len(c), cap(c)) c = append(c, 0) d = append(d, 0) p2 := &c[0] fmt.Printf("a[0] = %d pointer=%d, len=%d, cap=%d, p2 = %d \n", c[0], &c[0], len(c), cap(c), *p2) c = append(c, 1) c[0] = 2 fmt.Printf("a[0] = %d pointer=%d, len=%d, cap=%d, p2 = %d \n", c[0], &c[0], len(c), cap(c), *p2) /* c[0]=0, p2 = 0 c[0]=2, p2 = 0 in http://play.golang.org/p/DuClFZt2cK, the cap increasement by machine dependency. c[0]=0, p2 = 0 c[0]=2, p2 = *2* */ }