如果你看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 := 1Counter(&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 := 10b := 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 inttype Pointer *ArbitraryTypefunc Sizeof(x ArbitraryType) uintptrfunc Offsetof(x ArbitraryType) uintptrfunc Alignof(x ArbitraryType) uintptr
官网文档unsafe.Pointer的应用有以下阐明:
Pointer represents a pointer to an arbitrary type. There are four special operationsavailable 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能够被转换为uintptrPointer therefore allows a program to defeat the type system and read and writearbitrary memory. It should be used with extreme care. // 因而Pointer容许程序不按类型零碎的要求来读写任意的内存,应该十分小心地应用它。
所以unsafe.Pointer做的次要是用来进行桥接,用于不同类型的指针进行相互转换。
在任何状况下,后果都必须持续指向原调配的对象。
4.unsafe.Pointer,uintptr与一般指针的相互转换
unsafe.Pointer和一般指针的互相转换
var f float64 = 1.0fmt.Println(Float64bits(f))// 4607182418800017408func 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 s2j = *(*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指针,并可能导致程序解体
unsafe.Pointer和uintrptr的转换
package mainimport ( "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))}