【go笔记】指针, uintptr, unsafe.Pointer

Page content

今天简单整理一下以下三种指针: 普通指针(*T), uintptr, unsafe.Pointer

1. 普通指针(*T)

普通指针类型,用于传递对象地址,不能进行指针运算。 可以用 &(取地址) *(根据地址取值)。
这一块都很熟悉了,所以不在做过多的解释了。

2. uintptr

uintptr是一个无符号的整型,它可以保存一个指针地址。
它可以进行指针运算。
uintptr无法持有对象, GC不把uintptr当指针, 所以uintptr类型的目标会被回收。
想取值需要转成unsafe.Pointer后, 需再转到相对应的指针类型。

uintptr 在 builtin的package里。源代码中是这么解释的。

package builtin

//uintptr is an integer type that is large enough to hold the bit pattern of any pointer.
//uintptr是一个能足够容纳指针位数大小的整数类型
type uintptr uintptr

3. unsafe.Pointer

unsafe.Pointer可以指向任意类型的指针。
不能进行指针运算,不能读取内存存储的值(想读取的话需要转成相对应类型的指针)。
它是桥梁,让任意类型的指针实现相互转换, 也可以转换成uintptr 进行指针运算。

Pointer是在unsafe的package里。
源代码中的解释超级长,所以在说明文档中把部分内容拿来了。

type Pointer *ArbitraryType

Pointer represents a pointer to an arbitrary type. There are four special operations
available 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可以被转换为uintptr
Pointer therefore allows a program to defeat the type system and read and write
arbitrary memory. It should be used with extreme care.                //  因此Pointer允许程序不按类型系统的要求来读写任意的内存,应该非常小心地使用它。

一般的指针运算会走一下三个步骤。 1.将unsafe.Pointer转换为uintptr => 2.对uintptr执行算术运算 => 3.将uintptr转换回unsafe.Pointer,然后转成访问指向的对象的指针类型。

下面看看简单的测试。

简单的实践

可以看看下面测试, 数组和struct是可以使用指针偏移指向下一个元素。

 a := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
 b := unsafe.Pointer(uintptr(unsafe.Pointer(&a[0])) + 9*unsafe.Sizeof(a[0]))

 // b是 unsafe.Pointer 所以可转任意指针,转成(*int)指针后在取值
 fmt.Printf("b: %v, unsafe.Sizeof(a[0]): %d\n", *(*int)(b), unsafe.Sizeof(a[0])) //b: 9, unsafe.Sizeof(a[0]): 8

 c := unsafe.Pointer(uintptr(unsafe.Pointer(&a)) + uintptr(16)) //int是8位长度 所以16 等于 16/8 挪动了2位,所以下面结果是2
 fmt.Printf("c: %v\n", *(*int)(c))                              //c: 2

 user := user{id: 1, age: 10, name: "user1"}
 namePointer := unsafe.Pointer(uintptr(unsafe.Pointer(&user)) + unsafe.Offsetof(user.name))

 //这也一样 name是 unsafe.Pointer 所以可转任意指针,转成(*string)指针后在取值
 fmt.Printf("name: %v\n", *(*string)(namePointer)) //name: user1

再看看slice和map相关的简单测试。

 //因slice的结构是 => |ptr|len|cap
 s := make([]int, 5, 10)
 var Len = *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + uintptr(8))) //挪一个位置是Len
 fmt.Println(Len, len(s))                                                    // 5 5

 var Cap = *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + uintptr(16))) //挪二个位置是CAP
 fmt.Println(Cap, cap(s))                                                     // 10 10

 mp := make(map[string]int)
 mp["a"] = 11
 mp["b"] = 22

 //因map结构中第一个是元素个数,所以可以直接转成len
 count := **(**int)(unsafe.Pointer(&mp))
 fmt.Println(count, len(mp)) // 2 2

欢迎大家的意见和交流

email: li_mingxie@163.com