当 Go 将切片移动到内存中的另一个位置时,指向元素的指针会发生什么情况?

What happens to pointer to element when Go moves slice to another place in memory?

提问人:Daniel Richter 提问时间:12/12/2022 最后编辑:Jonathan HallDaniel Richter 更新时间:12/13/2022 访问量:336

问:

我有以下代码

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 的切片。它的 len 是 1,cap 也是 1。然后,我将指针指向它的第一个元素,并获取打印中的基础指针值。正如预期的那样,它工作正常。

比我向切片添加 3 个元素,使 go 扩展切片的容量,从而将其复制到内存中的另一个位置。之后,我打印切片第一个元素的地址(通过指针),该元素现在与存储在 中的地址不同。b

但是,当我打印它的基础值时,它也可以正常工作。我不明白它为什么有效。据我所知,第一个元素指向的切片被复制到内存中的另一个位置,因此它以前的内存一定已被释放。然而,它似乎仍然存在。bb

如果我们在地图上查看,golang 甚至不允许我们逐键创建元素指针,因为存在完全相同的问题 - 基础数据可以移动到内存中的另一个位置。但是,它与切片配合得很好。为什么会这样?它到底是如何工作的?内存是否未被释放,因为仍然有一个变量指向此内存?它与地图有何不同?

Go 指针 内存 哈希图 切片

评论

2赞 Volker 12/12/2022
“所以它之前的记忆一定是被释放了。”不,这不是切片的工作方式。切片是后备数组的视图,第一个后备数组在保留副本到它(元素)时保持活动状态。阅读有关切片和追加的博客文章。
2赞 kostix 12/12/2022
“所以它之前的记忆一定是被释放了”这是关键的误区。Go 是一种垃圾回收语言,因此没有明确的方法来“释放”其中的(释放)内存:当内存块没有引用它时,它就有资格获得 GC。在您的示例中,切片本身是对其后备数组的唯一引用,并且您添加了另一个。然后第一个消失了(由于调用而产生的切片引用了一个新的内存块),但第二个(你的)仍然活着,它引用的整个支持数组也还活着。append
2赞 Hymns For Disco 12/12/2022
Go 不会“移动”内存中的东西,但它会“复制”。您在相同的上下文中使用这两个词,这表明了混淆的根源。“移动”和“复制”是不一样的。
3赞 kostix 12/12/2022
你问题的最后一点在这里由@Volker回答。

答:

6赞 Jonathan Hall 12/12/2022 #1

当 Go 将切片移动到内存中的另一个位置时,指向元素的指针会发生什么情况?

无。

[W]然后我打印它的基础值也很好用。我不明白它为什么有效。b

为什么它不起作用?

最初指向的内存位置仍然存在,没有改变。只要任何内容(例如)仍然引用它,它就会保持可用。一旦删除了对该内存的所有引用(即超出范围),垃圾回收器可能会允许它被其他东西使用。b

1赞 Robert Ford 12/13/2022 #2

当 Go 将切片移动到另一个元素时,指向元素的指针会发生什么情况 在记忆中的位置?

我相信当前的 GC 实现根本不会移动此类对象,尽管规范允许这种情况发生。除非你使用“不安全”的包,否则你不太可能遇到任何问题,即使它确实移动了底层数据结构。