原文地址:https://blog.fanscore.cn/p/33/html
void *
,它与地址上的对象存在引用关系,垃圾回收器会由于有一个unsafe.Pointer类型的值指向某对象而不回收该对象。理论上说指针不过是一个数值,即一个uint,但实际上在go中unsafe.Pointer是不能经过强制类型转换为一个uint的,只能将unsafe.Pointer强制类型转换为一个uintptr。golang
var v1 float64 = 1.1 var v2 *float64 = &v1 _ = int(v2) // 这里编译报错:cannot convert unsafe.Pointer(v2) (type unsafe.Pointer) to type uint
可是能够将一个unsafe.Pointer强制类型转换为一个uintptr:多线程
var v1 float64 = 1.1 var v2 *float64 = &v1 var v3 uintptr = uintptr(unsafe.Pointer(v2)) v4 := uint(v3) fmt.Println(v3, v4) // v3和v4打印出来的值是相同的
能够理解为uintptr是专门用来指针操做的uint。
另外须要指出的是指针不能直接转为uintptr,即ui
var a float64 uintptr(&a) 这里会报错,不容许将*float64转为uintptr
经过上面的描述若是你仍是一头雾水的话,不妨看下下面这个实际案例:atom
package foo type Person struct { Name string age int }
上面的代码中咱们在foo包中定义了一个结构体Person
,只导出了Name
字段,而没有导出age
字段,就是说在另外的包中咱们只能直接操做Person.Name
而不能直接操做Person.age
,可是利用unsafe
包能够绕过这个限制使咱们可以操做Person.age
。线程
package main func main() { p := &foo.Person{ Name: "张三", } fmt.Println(p) // *Person是不能直接转换为*string的,因此这里先将*Person转为unsafe.Pointer,再将unsafe.Pointer转为*string pName := (*string)(unsafe.Pointer(p)) *pName = "李四" // 正常手段是不能操做Person.age的这里先经过uintptr(unsafe.Pointer(pName))获得Person.Name的地址 // 经过unsafe.Sizeof(p.Name)获得Person.Name占用的字节数 // Person.Name的地址 + Person.Name占用的字节数就获得了Person.age的地址,而后将地址转为int指针。 pAge := (*int)(unsafe.Pointer((uintptr(unsafe.Pointer(pName)) + unsafe.Sizeof(p.Name)))) // 将p的age字段修改成12 *pAge = 12 fmt.Println(p) }
打印结果为:指针
$ go run main.go &{张三 0} &{李四 12}
须要注意的是下面这段代码比较长:code
pAge := (*int)(unsafe.Pointer((uintptr(unsafe.Pointer(pName)) + unsafe.Sizeof(p.Name))))
可是尽可能不要分红两段代码,像这样:htm
temp := uintptr(unsafe.Pointer(pName)) + unsafe.Sizeof(p.Name)) pAge := (*int)(unsafe.Pointer(temp)
缘由是在第二行语句时,已经没有指针指向p
了,这时p
可能会回收掉了,这时获得的地址temp就是个野指针了,不知道指向谁了,是比较危险的。对象
另一个缘由是在当前Go(golang版本:1.14)的内存管理机制中不会迁移内存,可是不保证之后的版本内存管理机制中有迁移内存的操做,一旦发生了内存迁移指针地址发生变动,上面的分段代码就有可能出现严重问题。
关于Go的内存管理能够参看这篇文章:https://draveness.me/golang/docs/part3-runtime/ch07-memory/golang-memory-allocator/,读完这篇文章相信你就能理解上面的内存迁移问题。
除了上面两点外还有一个缘由是在Go 1.3上,当栈须要增加时栈可能会发生移动,对于下面的代码:
var obj int fmt.Println(uintptr(unsafe.Pointer(&obj))) bigFunc() // bigFunc()增大了栈 fmt.Println(uintptr(unsafe.Pointer(&obj)))
彻底有可能打印出来两个地址。
经过上面的例子应该明白了为何这个包名为unsafe,由于使用起来确实有风险,因此尽可能不要使用这个包。
我之因此研究unsafe.Pointer彻底是由于我要在多线程的环境中采用原子操做避免竞争问题,因此我用到了atomic.LoadPointer(addr *unsafe.Pointer)
。不过我后面发现了atomic包提供了一个atomic.Value
结构体,这个结构体提供的方法使我避免显式使用了unsafe.Pointer。因此你也正在使用atomic.LoadPointer()
不妨看看atomic.Value
是否是能够解决你的问题,这是我一点提醒。