原文地址: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
是否是能夠解決你的問題,這是我一點提醒。