指针, uintptr, unsafe.Pointer

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

1. 普通指针(*T) #

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

package main

import (
	"fmt"
)

// 函数式选项模式
type Option func(*Person)

type Person struct {
	Name string
	Age int
	Gender byte
	Usage func()
}

func NewPerson(name string,ops ...Option) *Person  {
	p := &Person{
		Name: name,
	}
	for _,opt := range ops {
		opt(p)
	}

	return p
}

func SetAge(age int) Option  {
	return func(person *Person) {
		person.Age = age
	}
}

func SetGender(gender byte) Option  {
	return func(person *Person) {
		person.Gender  = gender
	}
}

func SetUsage(fn func()) Option {
	return func(person *Person) {
		person.Usage = fn
	}
}

var (
	i int = 20
)


func main()  {
	p := NewPerson("laozhu",
			SetAge(10),
			SetGender(1),
			SetUsage(func() {
				fmt.Println("this is person struct")
			}),
		)

	fmt.Printf("%[1]T,%[1]+v\n",p)
	p.Filter()
	fmt.Printf("%[1]T,%[1]v,%[2]T,%[2]v",i,&i)
}

#output
*main.Person,&{%!+(string=laozhu) %!+(int=10) %!+(uint8=1) %!+(func()=0xc79a00)}v
this is person struct
int,20,*int,0xd26238

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

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,然后转成访问指向的对象的指针类型

上面步骤中unsafe.Pointer为一级指针、uintptr为二级指针

package main

import (
	"fmt"
	"unsafe"
)

type fn1 func()

func main()  {
	t := test()

	// 1、unsafe.Pointer(&t) *T转化为unsafe.Pointer指针
	// 2、(uintptr)(unsafe.Pointer(&t)) unsafe.Pointer转化为uintptr
	// 3、uintptr转化为unsafe.Pointer
	// 4、*(*fn1)(addr) 先转化为test函数地址*T,并取地址中的值
	addr := unsafe.Pointer((uintptr)(unsafe.Pointer(&t)))
	f1 := *(*fn1)(addr)
	f1()
	fmt.Printf("%+v,%+v,%+v",addr,&t,f1)
}

func test() fn1  {
	return func() {
		fmt.Println("aaaaaaaa")
	}
}

#output
aaaaaaaa
0xc000006028,0xc000006028,0xa79660

unsafe包

type ArbitraryType int
type Pointer *ArbitraryType

//值所对应变量在内存中的大小
func Sizeof(x ArbitraryType) uintptr
//结构体中成员的偏移量(实践代码中namePointer := unsafe.Pointer(uintptr(unsafe.Pointer(&user)) + unsafe.Offsetof(user.name))
func Offsetof(x ArbitraryType) uintptr
//值所对应的变量在内存地址中的几个字节对齐
func Alignof(x ArbitraryType) uintptr

实践 #

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

package main

import (
	"fmt"
	"unsafe"
)

type user struct {
	id int
	age int
	name string
}

func main()  {
	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