Go之unsafe.Pointer && uintptr類型

Go之unsafe.Pointer && uintptr類型golang

unsafe.Pointer

這個類型比較重要,它是實現定位欲讀寫的內存的基礎。官方文檔對該類型有四個重要描述:安全

(1)任何類型的指針均可以被轉化爲Pointer
(2)Pointer能夠被轉化爲任何類型的指針
(3)uintptr能夠被轉化爲Pointer
(4)Pointer能夠被轉化爲uintptr

大多數指針類型會寫成T,表示是「一個指向T類型變量的指針」。unsafe.Pointer是特別定義的一種指針類型(譯註:相似C語言中的void類型的指針),它能夠包含任意類型變量的地址。固然,咱們不能夠直接經過*p來獲取unsafe.Pointer指針指向的真實變量的值,由於咱們並不知道變量的具體類型。和普通指針同樣,unsafe.Pointer指針也是能夠比較的,而且支持和nil常量比較判斷是否爲空指針。函數

一個普通的T類型指針能夠被轉化爲unsafe.Pointer類型指針,而且一個unsafe.Pointer類型指針也能夠被轉回普通的指針,被轉回普通的指針類型並不須要和原始的T類型相同。ui

經過將float64類型指針轉化爲uint64類型指針,咱們能夠查看一個浮點數變量的位模式。spa

package main

import (
	"fmt"
	"unsafe"
	"reflect"
)

func Float64bits(f float64) uint64 {
	fmt.Println(reflect.TypeOf(unsafe.Pointer(&f)))  //unsafe.Pointer
	fmt.Println(reflect.TypeOf((*uint64)(unsafe.Pointer(&f))))  //*uint64
	return *(*uint64)(unsafe.Pointer(&f))
}

func main() {
	fmt.Printf("%#016x\n", Float64bits(1.0)) // "0x3ff0000000000000"
}

再看一個例子,.net

package main

import (
	"fmt"
	"reflect"
)

func main() {

	v1 := uint(12)
	v2 := int(12)

	fmt.Println(reflect.TypeOf(v1)) //uint
	fmt.Println(reflect.TypeOf(v2)) //int

	fmt.Println(reflect.TypeOf(&v1)) //*uint
	fmt.Println(reflect.TypeOf(&v2)) //*int

	p := &v1

	//兩個變量的類型不一樣,不能賦值
	//p = &v2 //cannot use &v2 (type *int) as type *uint in assignment

	fmt.Println(reflect.TypeOf(p)) // *unit
}
當再次把 v2 的指針賦值給p時,會發生錯誤cannot use &v2 (type *int) as type *uint in assignment,也就是說類型不一樣,一個是*int,一個是*uint。

這時,可使用unsafe.Pointer進行轉換,以下,指針

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {

	v1 := uint(12)
	v2 := int(13)

	fmt.Println(reflect.TypeOf(v1)) //uint
	fmt.Println(reflect.TypeOf(v2)) //int

	fmt.Println(reflect.TypeOf(&v1)) //*uint
	fmt.Println(reflect.TypeOf(&v2)) //*int

	p := &v1

	p = (*uint)(unsafe.Pointer(&v2)) //使用unsafe.Pointer進行類型的轉換

	fmt.Println(reflect.TypeOf(p)) // *unit
	fmt.Println(*p)                //13
}

關於unsafe.Pointer的其它用法請參見:http://my.oschina.net/goal/blog/193698code

uintptr

// uintptr is an integer type that is large enough to hold the bit pattern of
// any pointer.
type uintptr uintptr

uintptr是golang的內置類型,是能存儲指針的整型,在64位平臺上底層的數據類型是,blog

typedef unsigned long long int  uint64;
typedef uint64          uintptr;

一個unsafe.Pointer指針也能夠被轉化爲uintptr類型,而後保存到指針型數值變量中(注:這只是和當前指針相同的一個數字值,並非一個指針),而後用以作必要的指針數值運算。(uintptr是一個無符號的整型數,足以保存一個地址)這種轉換雖然也是可逆的,可是將uintptr轉爲unsafe.Pointer指針可能會破壞類型系統,由於並非全部的數字都是有效的內存地址。內存

許多將unsafe.Pointer指針轉爲原生數字,而後再轉回爲unsafe.Pointer類型指針的操做也是不安全的。好比下面的例子須要將變量x的地址加上b字段地址偏移量轉化爲*int16類型指針,而後經過該指針更新x.b:

package main

import (
	"fmt"
	"unsafe"
)

func main() {

	var x struct {
		a bool
		b int16
		c []int
	}

	/**
	unsafe.Offsetof 函數的參數必須是一個字段 x.f, 而後返回 f 字段相對於 x 起始地址的偏移量, 包括可能的空洞.
	*/

	/**
	uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
	指針的運算
	*/
	// 和 pb := &x.b 等價
	pb := (*int16)(unsafe.Pointer(uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
	*pb = 42
	fmt.Println(x.b) // "42"
}

上面的寫法儘管很繁瑣,但在這裏並非一件壞事,由於這些功能應該很謹慎地使用。不要試圖引入一個uintptr類型的臨時變量,由於它可能會破壞代碼的安全性(注:這是真正能夠體會unsafe包爲什麼不安全的例子)。

下面段代碼是錯誤的:

// NOTE: subtly incorrect!
tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
pb := (*int16)(unsafe.Pointer(tmp))
*pb = 42

產生錯誤的緣由很微妙。有時候垃圾回收器會移動一些變量以下降內存碎片等問題。這類垃圾回收器被稱爲移動GC。當一個變量被移動,全部的保存改變量舊地址的指針必須同時被更新爲變量移動後的新地址。從垃圾收集器的視角來看,一個unsafe.Pointer是一個指向變量的指針,所以當變量被移動是對應的指針也必須被更新;可是uintptr類型的臨時變量只是一個普通的數字,因此其值不該該被改變。上面錯誤的代碼由於引入一個非指針的臨時變量tmp,致使垃圾收集器沒法正確識別這個是一個指向變量x的指針。當第二個語句執行時,變量x可能已經被轉移,這時候臨時變量tmp也就再也不是如今的&x.b地址。第三個向以前無效地址空間的賦值語句將完全摧毀整個程序!

=========END=========

相關文章
相關標籤/搜索