切割出的 slice 預設 cap 吃到底,append 會寫進鄰居的地盤
把一份資料切成前半與後半,只 append 前半——後半的內容會被寫壞。因為切出來的 slice 預設 cap 一路吃到 array 結尾,前半的「還能往右寫」的範圍,正好就是後半的地盤。
path := []byte("AAAA/BBBBBBBBB")
sepIndex := bytes.IndexByte(path, '/') // 4
dir1 := path[:sepIndex] // len=4 cap=14 ← cap 吃到底
dir2 := path[sepIndex+1:] // len=9 cap=9
dir1 = append(dir1, "suffix"...)
// dir1 → AAAAsuffix
// dir2 → uffixBBBB ← 只動 dir1,dir2 卻壞了 01234567890123
path AAAA/BBBBBBBBB
dir1 AAAA
dir2 BBBBBBBBB
--- append 後 ---
dir1 AAAAsuffix
dir2 uffixBBBBdir1 的 cap 是 14,append 6 個字元沒超過,於是 就地寫入——寫進的正是 dir2 看的那段記憶體。
解法:full slice expression input[low:high:max]
第三個數把 cap 限成 max - low:
dir1 := path[:sepIndex:sepIndex] // len=4 cap=4它不是「當下複製一份出來」。 這一行沒有配置任何記憶體、沒有複製任何東西,指標仍指向同一塊 array 的同一個位置;它只是把 cap 寫死,等於預先設下一道路障:下一次 append 必然超過 cap,被迫 relocate,這時才真的搬家。
| 寫法 | 何時配置新記憶體 | 結果 |
|---|---|---|
path[:4](cap=14) | append 6 個字元不會配置 | 寫進鄰居的地盤 💥 |
path[:4:4](cap=4) | append 時必定配置 | 兩者從此無關 ✅ |
copy 到新 slice | 當下就配置 + 複製 | 兩者從此無關 ✅ |
要「當下就複製」得用 copy;full slice expression 只是把複製推遲到 append 那一刻,並保證它會發生。
另一個副作用:擋住 GC
只取原 slice 的一小段,其餘元素仍活在同一塊底層 array 裡,整個 array 都不能被回收。只需要一小部分時,該用 copy 產生新的 slice 與新的 array。