基本介绍

unsafe.Pointer类似于C语言中的void*类型,可以包含指向任意类型的指针,也可以借助unsafe.Pointer将类型A的指针转换为类型B的指针(前提是A的大小不小于B,当然A大小小于于B也能转,就是会有一部分内存的值为随机值)

  1. func main() {
  2. a := int16(1)
  3. b := (*int32)(unsafe.Pointer(&a))
  4. fmt.Println(*b) // 结果不确定
  5. }

一下方法可以查看浮点数的编码值

  1. func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) }
  2. func main() {
  3. fmt.Printf("%#016x\n", Float64bits(1.0)) // "0x3ff0000000000000"
  4. }

指针计算

unsafe.Pointer不支持指针计算,需要将其转换为uintptr类型

  1. var x struct {
  2. a bool
  3. b int16
  4. c []int
  5. }
  6. // 和 pb := &x.b 等价
  7. pb := (*int16)(unsafe.Pointer(uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
  8. *pb = 42
  9. fmt.Println(x.b) // "42"

不要在临时变量中保存uintptr类型的值,这会导致一个很微妙的错误。

  1. // !!错误示例
  2. tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
  3. pb := (*int16)(unsafe.Pointer(tmp))
  4. *pb = 42

有时候垃圾回收器会移动一些变量以降低内存碎片等问题。这类垃圾回收器被称为移动GC。当一个变量被移动,所有的保存该变量旧地址的指针必须同时被更新为变量移动后的新地址。从垃圾收集器的视角来看,一个unsafe.Pointer是一个指向变量的指针,因此当变量被移动时对应的指针也必须被更新;但是uintptr类型的临时变量只是一个普通的数字,所以其值不应该被改变。上面错误的代码因为引入一个非指针的临时变量tmp,导致垃圾收集器无法正确识别这个是一个指向变量x的指针。当第二个语句执行时,变量x可能已经被转移,这时候临时变量tmp也就不再是现在的&x.b地址。第三个向之前无效地址空间的赋值语句将彻底摧毁整个程序!