何时只通过`uintptr`来引用一个对象?

时间:2017-02-06 12:10:45

标签: go garbage-collection

Go编程语言在第13.2节中说这是代码是安全的,x将始终如此 对垃圾收集器可见:

pb := (*int16)(unsafe.Pointer(
  uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
*pb = 42

并且此代码不安全,因为x暂时不可见 垃圾收集器,它可以移动它,使pb成为悬空指针:

tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
pb := (*int16)(unsafe.Pointer(tmp))
*pb = 42

但我无法看到这两个例子之间的区别。

在描述为安全的情况下,在调用uintptr之后,唯一的 对x的引用是uintptr值,不是吗?有一个Pointer 它在同一条线上,但它是uintptr的一个参数,它已运行, 所以没有任何东西引用参数,因此Pointer不是活的,uintptr是对象的唯一引用。

我无法看到如何将uintptr存储在本地变量中而不是存储在本地变量中 表达中间值使它更安全。 Aren的局部变量 像编译器阶段中删除tmp一样,成为匿名数据流边缘, 这样生成的代码应该在语义上等效?或者Go有 垃圾收集何时可以运行的一些规则?比如只有安全点 之间的陈述?但是第一个例子中的代码有方法调用,所以我 会假设他们永远是安全点吗?

1 个答案:

答案 0 :(得分:1)

在我的评论here

中找到了我提到的参考资料
  

uintptr是整数,而不是引用。将指针转换为uintptr会创建一个没有指针语义的整数值。即使uintptr保存某个对象的地址,如果对象移动,垃圾收集器也不会更新该uintptr的值,uintptr也不会阻止该对象被回收。

这意味着这个表达式:

pb := (*int16)(unsafe.Pointer(
  uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
*pb = 42

是安全的,因为您正在创建一个uintptr,它被视为整数,而不是参考,但会立即分配(除非其他地方出现竞争条件, x引用的对象不能是GC' ed,直到分配后)。 uintptr(再次:整数类型)也会立即转换为指针,将其转换为引用,以便GC管理pb。这意味着:

  • uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)):一切都安全,因为x显然是对象的现有引用
  • pb被分配一个整数(通过强制转换)标记为对int16对象的引用

但是,当你这样写:

tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
pb := (*int16)(unsafe.Pointer(tmp))

在分配tmp(记住整数,而不是引用)之间,有可能移动内存中的实际对象。正如文档中所述:tmp更新。因此,当您指定pb时,最终可能会出现无效指针 在这种情况下,将tmp视为第一种情况中的x。它不是一个对象的引用,而是好像你写了

tmp := 123456 // a random integer
pb := (*int16) (unsafe.Pointer(tmp)) // not safe, obviously

例如:

var pb *int16
tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
go func() {
    time.Sleep(1 * time.Second)
    pb = (*int16)(unsafe.Pointer(tmp))
}()
// original value of x could be GC'ed here, before the goroutine starts, or the time.Sleep call returns
x = TypeOfX{
    b: 123,
}