英文:
When is it safe in Go to reference an object only through a `uintptr`?
问题
《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
存储在局部变量中而不是作为表达式中间值会使其更安全。局部变量像tmp
不是在编译器阶段被移除,变成匿名数据流边缘吗?因此生成的代码在语义上应该是等价的。或者Go语言是否有一些关于垃圾收集运行时的规则?比如只在语句之间有安全点?但是第一个示例中的代码有方法调用,所以我认为它们应该始终是安全点?
英文:
The Go Programming Language says in Section 13.2 that this is code is safe and x
will always
be visible to the garbage collector:
pb := (*int16)(unsafe.Pointer(
uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
*pb = 42
And that this code is unsafe, because x
is temporarily not visible to the
garbage collector, which could move it, making pb
a dangling pointer:
tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
pb := (*int16)(unsafe.Pointer(tmp))
*pb = 42
But I can't see the difference between these two examples.
In the case described as safe, after uintptr
has been called, the only
reference to the x
is the uintptr
value, isn't it? There's a Pointer
to it on the same line, but it was an argument to uintptr
, which has run,
so nothing is referencing the arguments, and so the Pointer
is not live and the uintptr
is the only reference to the object.
I can't see how storing the uintptr
in a local variable instead of as an
expression intermediate value makes it any more safe. Aren't local variables
like tmp
removed in compiler phases anyway, becoming anonymous dataflow edges,
so that the generated code should be semantically equivalent? Or does Go have
some rules for when garbage collection can run? Such as having safepoints only
between statements? But the code in the first example has method calls so I
would presume they would always be safepoints?
答案1
得分: 2
我找到了你在评论中提到的参考资料这里。
uintptr
是一个整数,而不是引用。将Pointer
转换为uintptr
会创建一个没有指针语义的整数值。即使uintptr
持有某个对象的地址,垃圾回收器也不会在对象移动时更新该uintptr
的值,也不会阻止该uintptr
对象被回收。
这意味着以下表达式是安全的:
pb := (*int16)(unsafe.Pointer(
uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
*pb = 42
因为你创建了一个 uintptr
,它被视为整数而不是引用,但它被立即赋值(除非在其他地方存在竞争条件,x
引用的对象不会被垃圾回收),直到赋值之后。uintptr
(再次强调:整数类型)也立即转换为指针,将其转换为引用,以便垃圾回收器可以管理 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 // 一个随机整数
pb := (*int16)(unsafe.Pointer(tmp)) // 显然不安全
例如:
var pb *int16
tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
go func() {
time.Sleep(1 * time.Second)
pb = (*int16)(unsafe.Pointer(tmp))
}()
// 在 goroutine 开始之前,或者 time.Sleep 调用返回之前,x 的原始值可能会被垃圾回收
x = TypeOfX{
b: 123,
}
英文:
Found the reference I hinted at in my comments here
> A uintptr is an integer, not a reference. Converting a Pointer to a uintptr creates an integer value with no pointer semantics. Even if a uintptr holds the address of some object, the garbage collector will not update that uintptr's value if the object moves, nor will that uintptr keep the object from being reclaimed.
What this means is that this expression:
pb := (*int16)(unsafe.Pointer(
uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
*pb = 42
Is safe because you're creating a uintptr
, which is seen as an integer, not a reference, but it's immediately assigned (unless there's a race condition somewhere else, the object that x
references cannot be GC'ed) until after the assignment). The uintptr
(again: integer type) is also immediately cast to a pointer, turning it into a reference so the GC will manage pb
. This means that:
uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b))
: all safe, becausex
clearly is an existing reference to an objectpb
is assigned an integer that is (through the cast) marked as a reference to anint16
object
However, when you write this:
tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
pb := (*int16)(unsafe.Pointer(tmp))
There is a chance that, between assigning tmp
(remember integer, not reference), the actual object in memory is moved. As it says in the docs: tmp
will not be updated. Thus, when you assign pb
, you could end up with an invalid pointer.<br>
think of tmp
in this case as x
in the first case. Rather than being a reference to an object, it's as if you wrote
tmp := 123456 // a random integer
pb := (*int16) (unsafe.Pointer(tmp)) // not safe, obviously
For example:
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,
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论