1161 字
约 3 分钟
5
S09-J01 · 切片底层:cap 增长、共享与内存泄漏
2024-12-31
2026-11-07

适用读者:被"append 之后原切片没变/被覆盖"搞晕过的人。
前置:S09-B04。
实测环境:Go 1.22.2(2026-11-05)。
覆盖旧文:—(E 驱动;B04 的机制在此深化)
覆盖扩展:E-S09-010(三索引切片)、E-S09-011(copy 切断共享)。
上一篇:S09-X01 · 学习路线与待补充清单
下一篇:S09-J02 · 字符串与 UTF-8。


1. 一句话结论

切片的三字段 {ptr, len, cap} 决定了三件事:能不能看到元素(len)、能不能原地追加(cap)、改了会不会影响别人(ptr 是否共享)。

你想做的事 看哪个字段 结果
读第 i 个元素 len 越界 panic
append 是否重新分配 cap - len 够 → 原地;不够 → 新数组
修改是否影响原数组 ptr 是否相同 相同 → 影响

2. append 的两种命运

append(s, x)
  ├─ len < cap → 原地写入 s[len],len++,ptr 不变(共享!)
  └─ len == cap → 新数组(容量翻倍),拷贝旧数据,ptr 变(独立)

第二种情况会"悄悄覆盖原数组"(当多个切片共享且容量足够时):

a := []int{1, 2, 3, 4}
b := a[:2]           // len=2, cap=4(共享)
b = append(b, 99)    // len<cap → 原地写 a[2] = 99
fmt.Println(a)       // [1 2 99 4] ← 被改了!

这条比"改下标"更隐蔽:你只是 append,却改了别的切片。


3. 三索引切片:给容量上锁(E-S09-010)

a := []int{1, 2, 3, 4, 5}
full := a[1:3]      // len=2 cap=4
limited := a[1:3:3] // len=2 cap=2  ← 上限设为 3

实测输出:

full len/cap: 2 4   limited len/cap: 2 2

然后 append 到 limited:

limited = append(limited, 99)
// 实测:limited = [2 3 99]   a = [1 2 3 4 5]  ← a 未受影响
写法 cap append 后是否影响原数组
a[1:3] 4 会(原地写)
a[1:3:3] 2 不会(超 cap → 新数组)

适用场景:把切片传给不可信代码(如插件、回调)时,用三索引锁住容量,防止对方 append 覆盖你的数据。


4. copy:彻底切断共享(E-S09-011)

c := make([]int, 2)
copy(c, a[1:3])
c[0] = 77
// 实测:c = [77 3]   a = [1 2 3 4 5]

copy 是唯一"复制数据"的标准做法(三索引只是限制容量,ptr 仍然共享)。

需求 做法
只是防 append 覆盖 三索引 a[l:h:h]
需要完全独立 copy(make([]T, len), s)
需要"快照"后原数组可继续被改 必须 copy

5. 内存泄漏:小切片持有大数组

func firstN(big []byte, n int) []byte {
    return big[:n]        // 只用了 n 字节,但底层数组是整个 big
}

问题:返回的切片持有 big 的 ptr → 整个大数组无法被 GC(即使你只用了前 n 字节)。

修法:

func firstN(big []byte, n int) []byte {
    out := make([]byte, n)
    copy(out, big[:n])
    return out            // 只持有 n 字节
}
场景 是否真的泄漏
长期存活的缓存/全局变量持有小切片 ✅ 是真泄漏
短生命周期(函数返回后很快释放) ⚠️ 影响有限

判据:"这个小切片会存活很久吗?" 会 → copy 一份。


6. 五个误区

误区 真相
"append 一定复制" 未超 cap 时原地写,可能覆盖原数组
"b := a[:n] 是独立副本" 共享底层数组
"三索引切片会复制数据" 只限制 cap,ptr 仍共享
"小切片一定省内存" 可能持有整个大数组(§5)
"cap 只在 append 时有用" 也决定"能不能原地写",进而决定副作用

7. 自检

  1. append 何时会覆盖原数组?为什么?
  2. a[1:3:3] 的 cap 是多少?为什么能阻止覆盖?
  3. 三索引与 copy 的区别?
  4. 什么情况下小切片会导致内存泄漏?怎么修?
  5. 如何判断两个切片是否共享底层数组?(提示:比较 &s[0])

8. 下一篇

S09-J02 · 字符串与 UTF-8:byte、rune 与 O(n) 陷阱

切片讲完,下一篇深入字符串:为什么按字节截断会切坏中文、range 的索引为何是字节偏移、以及安全的截断写法(含实测的"非法 UTF-8"输出)。


本篇属于系列《S09 · Go 语言》。扩展点 E-S09-010/011。三索引与 copy 行为为 Go 1.22.2 实测。

S09-J01 · 切片底层:cap 增长、共享与内存泄漏
https://www.clxhxhhr.top/posts/5306/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。