unsafe.Pointer和普通指针的转换
unsafe.Pointer
是一种特殊类型的指针,能够存放任何变量的地址。
一个普通的go类型指针*T
能够转换为一个unsafe.Pointer
,而且unsafe.Pointer
可以转换回普通的go类型指针,不一定转换回指针*T
。下面的实例代码将一个*float64
类型的指针转换为*uint64
类型的指针,然后得到该浮点数的16进制表示。
import (
"fmt"
"unsafe"
)
func Float64bits(f float64) uint64 {
return *(*uint64)(unsafe.Pointer(&f))
}
func main() {
fmt.Printf("%#016x\n", Float64bits(0.1))
}
通过unsafe.Pointer修改结构体的值
unsafe.Pointer
可以转换为uintptr类型,uintptr类型的变量包含指针的值,用来对地址进行数学运算。
下例演示了如何利用unsafe.Pointer
来给结构体的成员复制。
package main
import (
"fmt"
"unsafe"
)
func Float64bits(f float64) uint64 {
return *(*uint64)(unsafe.Pointer(&f))
}
func main() {
var x struct {
a bool
b int16
c []int
}
pb := (*int16)(unsafe.Pointer(uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
*pb = 16
fmt.Printf("%+v", x)
}
需要注意的是代码
pb := (*int16)(unsafe.Pointer(uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
不能分开写为:
tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
pb := (*int16)(unsafe.Pointer(tmp))
原因:一些垃圾回收器会移动变量在内存中的位置用来减少分片,这种类型的垃圾回收器称为 “moving GCs”。当一个变量被移动后,对应的指针都会更新到新的变量地址。unsafe.Pointer是一个指针,所以当变量移动后也会更新到新的地址,但uintprt是一个值,不会更新。上面错误代码中,tmp保存了变量x的地址的值,假设刚好第一行代码执行完后x被移动了,tmp由于是一个值,并不会更新到新的地址值,所以第2行代码使用的tmp就是一个错误的值