如果你看go的原始碼,尤其是runtime的部分的原始碼,你一定經常會發現unsafe.Pointer和uintptr這兩個函式,例如下面就是runtime里面的map原始碼實作里面的一個函式:
func (b *bmap) overflow(t *maptype) *bmap {
return *(**bmap)(add(unsafe.Pointer(b), uintptr(t.bucketsize)-sys.PtrSize))
}
那么這兩個方法有什么用呢?下面我們來重點介紹一下,
Go中的指標及與指標對指標的操作主要有以下三種:
一普通的指標型別,例如 var intptr *T,定義一個T型別指標變數,
二內置型別uintptr,本質是一個無符號的整型,它的長度是跟平臺相關的,它的長度可以用來保存一個指標地址,
三是unsafe包提供的Pointer,表示可以指向任意型別的指標,
1.普通的指標型別
count := 1
Counter(&count)
fmt.Println(count)
func Counter(count *int) {
*count++
}
普通指標可以通過參考來修改變數的值,這個跟C語言指標有點像,
2.uintptr型別
uintptr用來進行指標計算,因為它是整型,所以很容易計算出下一個指標所指向的位置,uintptr在builtin包中定義,定義如下:
// uintptr is an integer type that is large enough to hold the bit pattern of any pointer.
// uintptr是一個能足夠容納指標位數大小的整數型別
type uintptr uintptr
雖然uintpr保存了一個指標地址,但它只是一個值,不參考任何物件,因此使用的時候要注意以下情況:
1.如果uintptr地址相關聯物件移動,則其值也不會更新,例如goroutine的堆疊資訊發生變化
2.uintptr地址關聯的物件可以被垃圾回收,GC不認為uintptr是活參考,因此unitptr地址指向的物件可以被垃圾收集,
一個uintptr可以被轉換成unsafe.Pointer,同時unsafe.Pointer也可以被轉換為uintptr,可以使用使用uintptr + offset計算出地址,然后使用unsafe.Pointer進行轉換,格式如下:p = unsafe.Pointer(uintptr(p) + offset)
n := 10
b := make([]int, n)
for i:= 0;i< n;i++ {
b[i] = i
}
fmt.Println(b)
// [0 1 2 3 4 5 6 7 8 9]
// 取slice的最后的一個元素
end := unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + 9 * unsafe.Sizeof(b[0]))
// 等價于unsafe.Pointer(&b[9])
fmt.Println(*(*int)(end))
// 9
3.unsafe.Pointer
unsafe.Pointer是特別定義的一種指標型別,它可以包含任意型別變數的地址,Pointer在unsafe包中定義,定義如下:
package unsafe
// ArbitraryType is here for the purposes of documentation only and is not actually
// part of the unsafe package. It represents the type of an arbitrary Go expression.
// ArbitraryType在這里不是unsafe包的實際的一部分,僅僅是為了檔案記錄
type ArbitraryType int
type Pointer *ArbitraryType
func Sizeof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Alignof(x ArbitraryType) uintptr
官方檔案unsafe.Pointer的使用有以下說明:
Pointer represents a pointer to an arbitrary type. There are four special operations available for type Pointer that are not available for other types: // Pointer代表了一個任意型別的指標,Pointer型別有四種特殊的操作是其他型別不能使用的: - A pointer value of any type can be converted to a Pointer. // 任意型別的指標可以被轉換為Pointer - A Pointer can be converted to a pointer value of any type. // Pointer可以被轉換為任務型別的值的指標 - A uintptr can be converted to a Pointer. // uintptr可以被轉換為Pointer - A Pointer can be converted to a uintptr. // Pointer可以被轉換為uintptr Pointer therefore allows a program to defeat the type system and read and write arbitrary memory. It should be used with extreme care. // 因此Pointer允許程式不按型別系統的要求來讀寫任意的記憶體,應該非常小心地使用它,
所以unsafe.Pointer做的主要是用來進行橋接,用于不同型別的指標進行互相轉換,
在任何情況下,結果都必須繼續指向原分配的物件,
4.unsafe.Pointer,uintptr與普通指標的互相轉換
- unsafe.Pointer和普通指標的相互轉換
var f float64 = 1.0 fmt.Println(Float64bits(f)) // 4607182418800017408 func Float64bits(f float64) uint64 { return *((*uint64)(unsafe.Pointer(&f))) }借助unsafe.Pointer指標,實作float64轉換為uint64型別,當然,我們不可以直接通過
*p來獲取unsafe.Pointer指標指向的真實變數的值,因為我們并不知道變數的具體型別,另外一個重要的要注意的是,在進行普通型別轉換的時候,要注意轉換的前后的型別要有相同的記憶體布局,下面兩個結構也能完成轉換,就因為他們有相同的記憶體布局type s1 struct { id int name string } type s2 struct { field1 *[5]byte filed2 int } b := s1{name:"123"} var j s2 j = *(*s2)(unsafe.Pointer(&b)) fmt.Println(j) - unsafe.Pointer和uintrptr的互相轉換及配合
uintptr型別的主要是用來與unsafe.Pointer配合使用來訪問和操作unsafe的記憶體,unsafe.Pointer不能執行算術操作,要想對指標進行算術運算必須這樣來做:
1.將unsafe.Pointer轉換為uintptr
2.對uintptr執行算術運算
3.將uintptr轉換回unsafe.Pointer,然后訪問uintptr地址指向的物件
需要小心的是,上面的步驟對于垃圾收集器來說應該是原子的,否則可能會導致問題,
例如,在第1步之后,參考的物件可能被收集,如果在步驟3之后發生這種情況,指標將是一個無效的Go指標,并可能導致程式崩潰
package main
import (
"fmt"
"unsafe"
)
type Person struct {
age int
name string
}
func main() {
p := &Person{age: 30, name: "Bob"}
//獲取到struct s中b欄位的地址
p := unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Offsetof(p.name))
//將其轉換為一個string的指標,并且列印該指標的對應的值
fmt.Println(*(*string)(p))
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/255590.html
標籤:區塊鏈
