Golang學習筆記--unsafe.Pointer和uintptr

如果你看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))
}

 

 


 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章