2 回答

TA貢獻1856條經驗 獲得超5個贊
首先,對于任何帶有切片的內容,我建議您閱讀Go Slices: usage and internals。簡短的故事是 Go 使用 append 處理切片的容量可能很不穩定。
給定的切片變量具有三個組成部分:指向數據數組的底層指針、長度和容量。已經有很多關于差異的說法,但這里的重要部分是長度是(有效)當前使用的底層內存緩沖區的部分,容量是底層緩沖區的整體大小。這是一個不精確的定義,但它在實踐中效果很好。
神秘的下一部分是append內置函數。append 的功能有時實際上有點難以推理,這可能是 Go 中最大的問題之一:
如果底層緩沖區足夠大(cap > len),只需將 len 增加要添加的元素數并將數據放入新空間。
如果底層緩沖區不夠大,則分配一個更大容量的新緩沖區,將舊緩沖區復制到新緩沖區中,然后添加所有新元素。
2 的最大癥結在于,在追加之后對同一個切片進行兩次任意操作,很難知道舊的或新的內存緩沖區是否被先驗地影響。確實,讓我們試試這個:
var c []int
var d []int
c = append(c, 0)
d = append(d, 0)
p2 := &c[0]
fmt.Printf("c[0]=%d pointer=%d, p2 = %d\n", c[0], &c[0], *p2)
c = append(c, 1)
c[0] = 2
fmt.Printf("c[0]=%d pointer=%d, p2 = %d\n", c[0], &c[0], *p2)
c = append(c, 1)
c[0] = 25
fmt.Printf("c[0]=%d pointer=%d, p2 = %d\n", c[0], &c[0], *p2)
你會得到c[0]=25, p2=2。我們只添加了一個語句,突然間指針和切片值發散了!
這意味著上限發生了變化,或者更確切地說,使用了新的緩沖區。實際上,cap(c)在第一個 append 之后但在第三個之前打印,將產生2. 這意味著當將單個元素附加到容量為 0 的切片時,Go 會初始化 [footnote] 長度為 1 和容量為2的切片。所以在第二次追加之后沒有分配新的緩沖區,因為有空間。這就是為什么p2和c[0]在第二次追加之后是一樣的,但在第三次之后是一樣的。
一般來說,雖然切片和對內存中特定位置的引用的確切規則是一致的,但在實踐中切片增長的行為非常挑剔,通常最好永遠不要依賴指向切片值(或兩個切片變量)的指針具有相同的底層緩沖區),除非您計劃從不使用 append,或者將緩沖區預先分配make到使用 append 永遠不會重新分配的大小。
[腳注] 不完全正確,我想給出一個巨大的警告,即 append 后的確切容量取決于實現。請不要依賴 APPEND 導致編譯器之間或什至不同編譯器目標之間一致的容量
- 2 回答
- 0 關注
- 148 瀏覽
添加回答
舉報