Go语言是否保证常量的地址不变?

15

给定一个对象obj,是否有保证?

uintptr(unsafe.Pointer(&obj))

即使在任何时候调用,它都将始终评估为相同的值吗?

当然,Go保证如果您获取指向同一对象的两个指针,它们将始终相等。不过,实现可能会将对象移动到内存中并透明地更新对它的所有指针。

如果您考虑像标记压缩算法这样的垃圾收集策略,则此事很有趣。实现者是否允许使用这种垃圾收集策略?

3个回答

22

没有这样的保证,正是因为这样才能实现移动收集器。

事实上,虽然垃圾收集器今天不会移动堆对象,在Go 1.3中,当需要增长时,栈可以移动,因此完全有可能

var obj int
fmt.Println(uintptr(unsafe.Pointer(&obj)))
bigFunc()
fmt.Println(uintptr(unsafe.Pointer(&obj)))

会打印出两个不同的指针,因为bigFunc扩大了栈,导致obj和栈上的其他所有内容移动。


2
那么,sync.copyChecker中的代码是否利用了未定义的行为?将从Go分配的结构体传递给C是安全的吗?指针算术运算需要多“长”时间才能安全地避免与垃圾收集器竞争? - fuz
8
  1. 是的,sync.copyChecker使用了未定义的行为。标准库的好处是它与编译器版本绑定在一起,因此在这个地方使用未定义的行为实际上不是问题。
  2. 目前可以工作。我们尚未确定长期该怎么做。我更希望你在Go和C之间共享C指针而不是Go指针。
  3. 指针算术运算 - 转换为uintptr,添加/减去,再转换回unsafe.Pointer - 应该在单个语句中完成,不调用任何函数。我相信这样不会出错。
- Russ Cox

7
规范中没有任何保证这一点,可能是为了允许将来语言的实现使用紧凑垃圾收集器。在这个 golang-nuts 线程中,其中一位开发人员建议,只要将 unsafe.Pointer 值固定在内存中,就可以实现压缩式 GC,但这不能扩展到所有 unitptr 值。
对于当前的 Go 运行时环境,我认为这是正确的,但依赖它仍然是未定义的行为。但也有一些注意事项:
  1. 如果 obj 是零大小类型,则表达式的值可能不唯一,正如规范中所述

  2. 在程序的生命周期内,特定的 uintptr 值可能指向不同的对象。


我回答了你的问题。Go语言不能保证它是常量。 - James Henstridge
好的。我已经加强了答案第一段的语言。规范中没有说指针被固定(只是关于它们如何比较),这似乎是一个有意的选择。 - James Henstridge
也没有任何东西表明它们没有被固定。"固定指针"可能是隐含的。 - fuz
如果语言规范假定指针被固定,那么我在链接的邮件列表线程中提到的Go开发人员会这样说,而不是指出一些需要固定的有限情况。 - James Henstridge
你链接的讨论似乎只是一个假设情景。他们在讨论指针需要被固定的时候,这样看起来对象就不会移动了。根据我的理解,他们建议一旦你使用了 unsafe.Pointer,底层对象就会被固定住,直到程序结束时才会移动。对我来说,这似乎与你从帖子中读到的相反。 - fuz

3
没有绝对的保证。特别是如果Go将压缩功能添加到其标记和扫描垃圾回收器中。
存储在pointer type和类型unsafe.Pointer中的地址将由任何垃圾回收器在必要时进行更新。 存储在类型uintptr中的地址作为无符号整数将不会被垃圾回收器更新。 uintptr类型不是指针类型,而是整数类型。

数字类型

uintptr 一个无符号整数,足够大,可以存储指针值的未解释位

将unsafe.Pointers转换为uintptr

指针应始终保留在unsafe.Pointers中-而不是uintptrs中。

Russ

对于你的示例,

uintptr(unsafe.Pointer(&obj))

你有一个无符号整数,而不是一个地址。


据我所见,你提供的讨论更关注的是垃圾回收器可能会回收仅从uintptr引用的对象的问题。Dave Cheney甚至说过:“只要原始的unsafe.Pointer值在堆栈帧中仍然存在,将unsafe.Pointer转换为uintptr是可以的。”现在我更加困惑了。 - fuz
那个线程中的修复似乎是代码持有了一个 unintptr 并期望 GC 保持相关分配的存活。在精确 GC 中,只考虑实际指针变量,这种情况不会发生。 - James Henstridge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接