var a []int
func f(b []int) []int {
a = b[:2]
return a
}
func main() {
...
}
上面的代码会发生内存泄漏。
切片的底层结构:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
切片在运行时的表现为SliceHeader结构体,其定义如下:
Data: 执行具体底层数组的指针
Len:表示切片的长度
Cap:表示切片的容量(底层数组的容量)
切片真正存储数据的地方,是一个数组。切片的 Data 属性中存储的是指向所引用的数组指针地址。
内存泄露的原因:
在上述案例中,我们有一个包全局变量 a,共有 2 个切片 a 和 b,截取了 b 的一部分赋值给了 a,两者存在着关联。
打开程序底层来看,他应该是如下图所示:
切片 a 和 b 都共享着同一个底层数组(共享内存块),sliceB 包含全部所引用的字符。sliceA 只包含了 [:2],也就是 0 和 1 两个索引位的字符。
泄露的点,就在于虽然切片 b 已经在函数内结束了他的使命了,不再使用了。但切片 a 还在使用,切片 a 和 切片 b 引用的是同一块底层数组(共享内存块)。
切片 a 引用了底层数组中的一段。
虽然切片 a 只有底层数组中 0 和 1 两个索引位正在被使用,其余未使用的底层数组空间毫无作用。但由于正在被引用,他们也不会被 GC,因此造成了泄露。
解决方法:
思路:防止出现两个变量同时应用同一个底层数组。
利用切片的特性。当切片的容量空间不足时,会重新申请一个新的底层数组来存储,让两者彻底分开。
var a []int
var c []int // 第三者
func f(b []int) []int {
a = b[:2]
// 新的切片 append 导致切片扩容
c = append(c, b[:2]...)
fmt.Printf("a: %p\nc: %p\nb: %p\n", &a[0], &c[0], &b[0])
return a
}
返回结果:
a: 0xc00001e080
c: 0xc000018080
b: 0xc00001e080
新增一个临时变量c,它容量为0,使用append做追加,发生内存不足进行扩容,生成一个新的底层数组,将原本的切片设置为nil,完成了底层数组的区分。