Go之unsafe.Pointer && uintptr类型golang
这个类型比较重要,它是实现定位欲读写的内存的基础。官方文档对该类型有四个重要描述:安全
(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 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=========