你知道 Go 中的 unsafe.Pointer 和 uintptr吗

452 阅读2分钟

golang是一种强类型的静态语言,不支持不同类型之间的隐式转换,比如intint64这种,也不支持不同指针类型之间的转换,比如*int转换为*int64;当然也不支持指针运算的操作了

但是,在go中却提供了unsafe.Pointeruintptr这两种类型,来间接的完成不同指针类型的转化和指针的运算操作

unsafe.Pointer是什么

unsafe.Pointer是一个特殊的类型,可以持有任何类型的指针,就像是 C中的void *一样,比如:

func main(){
    k := 123
    p := unsafe.Pointer(&k)  //此时 p 就是一个指向 k 的指针
    fmt.Println(p)
}
## Output
0xc00004c1f0  // 输出的就是存储 k 的地址

但是,unsafe.Pointer表示的指针不能进行指针运算,也不能用*p的方式输出它指向的那个地址中的值;它就只是一个桥梁,用来将其转换为其他的指针类型,比如:

func main() {
    var k int64 = 123
    p := unsafe.Pointer(&k)
    var pp *int
    pp = (*int)(p)  //将其转化为了 *int 类型,然后使用, *pp 的方式输出它的值
    fmt.Println(pp)
    fmt.Println(*pp)
}
​
## output
0xc0000ba058
123
uintptr是什么

uintptr就是一个无符号的整型,它足够大能够保存任何类型的指针;它跟unsafe.Pointer不同,uintptr保存的是一个指针的十进制,比如:

func main() {
    k := 123
    h := unsafe.Pointer(&k)
    fmt.Println(h)
    o := uintptr(h)
    fmt.Println(o)
    fmt.Printf("%x\n",o)  //输出它的十六进制
}
​
### Output
0xc00000a0b0
824633761968
c00000a0b0

将其转化为十六进制就会发现其实它保存的它的十六进制值和unsafe.Pointer输出的是一样的;uintptr是可以进行指针运算的,比如:

func main() {
    k := 123
    h := unsafe.Pointer(&k)
    fmt.Println(h)
    o := uintptr(h)
    fmt.Println(o)
    fmt.Printf("%x\n",o)
    o = o + unsafe.Sizeof(k)
    fmt.Println(o)
    fmt.Printf("%x\n",o)
}
​
## Output
0xc00000a0b0
824633761968
c00000a0b0
824633761976
c00000a0b8

可以发现它的运算结果就是在原来的地址加了一个整型类型的大小;uintptr是不能直接转化为某个具体类型的,比如:

func main() {
    k := 123
    p := uintptr(k)
    var pp *int32
    pp = (*int32) (p)
}

这种就会编译失败,所以首先需要将其转化为unsafe.Pointer,然后再把它转化为具体的指针类型,比如:

func main() {
    k := 123
    p := uintptr(k)
    fmt.Println(p)
    fmt.Println(&p)
    u := unsafe.Pointer(&p)
    var pp *int32
    pp = (*int32) (u)
    fmt.Println(pp)
    fmt.Println(*pp)
}
​
## Output
123
0xc000126058
0xc000126058
123

这样就可以实现正常的转化了

总结

golang提供的这种操作指针的方式是很容易引起内存泄露的,所以没必要还是不要使用这种方式操作指针为好