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有
垃圾收集何时可以运行的一些规则?比如只有安全点
之间的陈述?但是第一个例子中的代码有方法调用,所以我
会假设他们永远是安全点吗?
答案 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,
}