Go切片内存迁移后原元素指针的有效性及与Map的差异
package main import "fmt" func main() { a := []int{1} b := &a[0] fmt.Println(a, &a[0], b, *b) // prints [1] 0xc00001c030 0xc00001c030 1 a = append(a, 1, 2, 3) fmt.Println(a, &a[0], b, *b) // prints [1 1 2 3] 0xc000100020 0xc00001c030 1 }
问题描述
初始时创建长度与容量均为1的int类型切片a,随后获取其首个元素的指针b,打印结果符合预期。当通过append向a中添加3个元素后,切片因容量不足触发扩容,底层数据被复制到新内存地址。此时打印发现切片a首个元素的地址已改变,但指针b指向的原地址仍能正常读取值。
存在以下疑问:
- 原切片的底层内存理应已被释放,为何还能访问?
- Go不允许获取Map元素的指针,原因是其底层数据可能迁移,但切片却可以,这两种情况有何差异?
- 是否因为指针
b仍引用原内存导致其未被释放?
问题解答
原底层内存可访问的原因
Go的垃圾回收采用可达性分析机制:只要内存块还有任何活跃的引用(比如这里的指针b),GC就不会回收这块内存。所以原底层数组的内存因为b的引用依然可达,没有被释放,自然能读取到值。但要注意,这种行为是未定义的——后续如果有其他内存分配操作,这块内存可能被覆盖,读取到的数值会变成不可预测的垃圾值,绝对不能在生产代码中依赖这种特性。切片与Map取元素指针的差异
- 切片的底层是连续的数组,你获取的是数组中某个元素的直接指针。只要这个指针存在,对应的数组内存就会被视为可达,不会被回收;即便切片扩容复制了数据,原数组只要有引用就会保留。
- Map的底层是哈希表结构,元素的存储位置会因为扩容、rehash或者删除操作发生迁移,而且Go的Map实现刻意禁止获取元素指针——因为一旦底层结构变动,之前获取的指针就会指向无效的内存位置,直接禁止这种操作是从根源避免潜在的内存安全问题。
指针
b是否导致原内存未被释放
是的,正是因为b还持有对原底层数组元素的引用,GC判定该内存块是可达的,所以不会对其进行回收。这也是你能继续读取到值的核心原因。
内容的提问来源于stack exchange,提问作者Daniel Richter
相关产品推荐
相关产品推荐

