学习目标
-
是什么
-
有什么用
-
如何用
-
常见应用场景
简介
unsafe.Pointer和uintptr从命名上看就知道和指针有关系。
unsafe.Pointer
是什么
是指针类型,是任意类型指针,可以指向任意类型,不像普通指针类型*T只能指向T类型。
源码:
type Pointer *ArbitraryType
type ArbitraryType int
官方文档描述:
Pointer represents a pointer to an arbitrary type. There are four special operations available for type Pointer that are not available for other types:
-
A pointer value of any type can be converted to a
Pointer. -
A
Pointercan be converted to a pointer value of any type. -
A
uintptrcan be converted to aPointer. -
A
Pointercan be converted to auintptr.
如图:
即,unsafe.Pointer作为“桥梁”
如何使用
如图:
官方文档描述:
The following patterns involving
Pointerare valid.Code not using these patterns is likely to be invalid today or to become invalid in the future.
Even the valid patterns below come with important caveats.
Running "go vet" can help find uses of
Pointerthat do not conform to these patterns, but silence from "go vet" is not a guarantee that the code is valid.
正确的使用方式有(来自官方文档,可看源码介绍):
- Conversion of a
*T1to Pointer to*T2. - Conversion of a
Pointerto auintptr(but not back toPointer). - Conversion of a
Pointerto auintptrand back, with arithmetic. - Conversion of a
Pointerto auintptrwhen calling syscall.Syscall. - Conversion of the result of
reflect.Value.Pointerorreflect.Value.UnsafeAddrfromuintptrtoPointer. - Conversion of a
reflect.SliceHeaderorreflect.StringHeaderData field to or fromPointer.
具体case:
实现不同类型指针的互转,如下:
func main() {
i := 3
iPtr := &i
j := (*int64)(unsafe.Pointer(iPtr))
fmt.Println(iPtr, j) // iPtr、j共用同一个指针,都指向i变量
fmt.Printf("%T %d\n", j, *j)
*j = 8
fmt.Println(i, *iPtr, *j)
}
输出:
0xc00001c088 0xc00001c088
*int64 3
8 8 8
uintptr
是什么
是Go内置类型,本质上就是整型,存储无符号整数,可存储指针值(即内存地址) ,源码:
// uintptr is an integer type that is large enough to hold the bit pattern of
// any pointer.
type uintptr uintptr
常用于指针运算。
怎么使用
实现指针运算:将普通指针类型*T转成unsafe.Pointer再转成uintptr,然后做加减法,再转回unsafe.Pointer,再转回普通指针类型,之后通过*操作符就能读/写指针指向的值了。
具体case:
通过指针偏移修改结构体成员,如下:
type Student struct {
Name string
Address string
}
func main() {
stu := Student{
Name: "张三",
Address: "aaa",
}
stuPtr := &stu
// 可以看到,「结构体的地址」和「结构体第一个成员的内存地址」是相等的
fmt.Printf("%p %p\n", stuPtr, &stuPtr.Name)
namePtr := (*string)(unsafe.Pointer(stuPtr))
fmt.Println("namePtr:", namePtr, ", value:", *namePtr)
*namePtr = "李四"
fmt.Println("stu:", stu)
// 转成uintptr进行指针运算,然后再转成任意类型指针Pointer,再转成具体类型指针*string
addressPtr := (*string)(unsafe.Pointer((uintptr)(unsafe.Pointer(stuPtr)) + unsafe.Offsetof(stuPtr.Address)))
fmt.Println(*addressPtr)
*addressPtr = "bbb"
fmt.Println("stu:", stu)
}
输出结果:
0xc0000b4000 0xc0000b4000
namePtr: 0xc0000b4000 , value: 张三
stu: {李四 aaa}
aaa
stu: {李四 bbb}
再看一个case:
func main() {
byts := []byte{1, 2, 3, 4}
ptr1 := uintptr(unsafe.Pointer(&byts[0]))
ptr2 := uintptr(unsafe.Pointer(&byts[1]))
ptr3 := uintptr(unsafe.Pointer(&byts[2]))
ptr4 := uintptr(unsafe.Pointer(&byts[3]))
// 输出各内存地址值,正好是连续的内存地址
fmt.Println(ptr1)
fmt.Println(ptr2)
fmt.Println(ptr3)
fmt.Println(ptr4)
i0 := (*byte)(unsafe.Pointer(ptr1))
i1 := (*byte)(unsafe.Pointer(ptr1 + 1)) // 内存地址加1个字节
i2 := (*byte)(unsafe.Pointer(ptr1 + 2)) // 内存地址加2个字节
i3 := (*byte)(unsafe.Pointer(ptr1 + 3)) // 内存地址加3个字节
fmt.Println(*i0, *i1, *i2, *i3)
var num int32 = 511 // 511的二进制:0000.....111111111,即23个0+9个1
// 转成*byte,所以仅访问一个字节的数据,即11111111
tmp := (*byte)(unsafe.Pointer(&num))
fmt.Println(*tmp) // 输出结果:255
// 内存地址加1个字节,再访问1个字节的数据,即00000001
tmp = (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&num)) + 1))
fmt.Println(*tmp) // 输出结果:1
}
输出结果:
824634330796
824634330797
824634330798
824634330799
1 2 3 4
255
1
unsafe.Pointer和uintptr的区别
本质是不同的:
-
unsafe.Pointer:本质是指针类型
-
uintptr:本质是整型,只是存储「指针值」(即内存地址)
总结:指针和指针的具体类型
指针,就是一个内存地址;
指针的具体类型,决定了该指针指向多大的内存区域,访问指针就是访问这块内存区域的数据。
具体case如下:
func main() {
var num int32 = 511 // 511的二进制:0000.....111111111,即23个0+9个1
// 转成*byte,所以仅访问一个字节的数据,即11111111
tmp := (*byte)(unsafe.Pointer(&num))
fmt.Println(*tmp) // 输出结果:255
// 内存地址加1个字节,再访问1个字节的数据,即00000001
tmp = (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&num)) + 1))
fmt.Println(*tmp) // 输出结果:1
}
输出结果:
255
1