douhuzhi0907 2017-02-06 12:10
浏览 40

什么时候仅通过`uintptr`引用对象是安全的?

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条回答 默认 最新

  • doutan6286 2017-02-06 12:27
    关注

    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, because x clearly is an existing reference to an object
    • pb is assigned an integer that is (through the cast) marked as a reference to an int16 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.
    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,
    }
    
    评论

报告相同问题?

悬赏问题

  • ¥15 Vue3 大型图片数据拖动排序
  • ¥15 划分vlan后不通了
  • ¥15 GDI处理通道视频时总是带有白色锯齿
  • ¥20 用雷电模拟器安装百达屋apk一直闪退
  • ¥15 算能科技20240506咨询(拒绝大模型回答)
  • ¥15 自适应 AR 模型 参数估计Matlab程序
  • ¥100 角动量包络面如何用MATLAB绘制
  • ¥15 merge函数占用内存过大
  • ¥15 使用EMD去噪处理RML2016数据集时候的原理
  • ¥15 神经网络预测均方误差很小 但是图像上看着差别太大