一,区别
1,指针类型
golang支持指针类型,指针类型的变量存的是一个内存地址,这个地址指向的内存空间存的才是一个具体的值。
比如int,int32,A(自定义结构体类型),string等,都是指针类型。
golang的指针类型和c/c++的指针类型基本一样,但是多了几个限制:
- 1,int,int32等不同的指针类型不能相互转化.
- 2,指针类型不支持c/c++这样的指针运算。
2,unsafe.Pointer类型
这个类型比较重要,它是实现定位和读写的内存的基础。go runtime大量使用它。官方解释是:
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 Pointer can be converted to a pointer value of any type.
- A uintptr can be converted to a Pointer.
- A Pointer can be converted to a uintptr.
根据官方解释,unsafe.Pointer类型可以指代上面说的任意一种指针类型。
- unsafe.Pointer类型可以和任意指针类型互转
- unsafe.Pointer类型可以和uintptr类型(后面介绍)互转
3, uintprt类型
uintptr是golang的内置类型。是能存储指针的整形。
type uintptr uintptr
在64位平台上,底层的数据类型是:
typedef unsigned long long int uint64;
typedef uint64 uintptr;
uintptr的用处也很大:
- uintptr可以和unsafe.Pointer类型互转(上面提到过)。
- uintptr可以做指针运算,这一点有时候很重要,但是依赖平台,同一类型变量在不同的平台占用的存储空间大小不一样,在用uintptr做指针运算的时候,偏移量也会相应的不一样(后面有例子说明)。
二,使用
上面介绍了指针,unsafe.Pointer和uintptr这三种类型,看起来这三种类型存的都是变量的内存地址,那为什么要有三种呢?关键就在于:
- unsafe.Pointer类型可以和另外两种类型互转
- uintptr类型可以做指针运算
- 指针类型可以方便的取变量值
把这三者结合起来,我们就可以方便的对变量做修改:
type MyStruct struct {
i int
j int
}
func myFunction(ms *MyStruct) {
ptr := unsafe.Pointer(ms)
for i := 0; i < 2; i++ {
c := (*int)(unsafe.Pointer((uintptr(ptr) + uintptr(8*i))))
*c += i + 1
fmt.Printf("[%p] %d\n", c, *c)
}
}
func main() {
a := &MyStruct{i: 40, j: 50}
myFunction(a)
fmt.Printf("[%p] %v\n", a, a)
}
这个例子是分析golang结构体在内存中的布局写的,*MyStruct是指针类型,我们想对i和j两个成员的值做修改。
为了拿到j的内存地址,先用uintptr(unsafe.Pointer(*MyStruct))来获取i的内存地址,然后用uintptr+8这种地址运算来得到成员j的内存地址,这要基于两点保证:
- 1,golang的struct结构体数据在内存中是一整块连续的。
- 2,+8是int类型在64位操作系统上占用8字节,同样的类型在不同的操作系统上占用内存空间是不一样的。
从这个例子我们可以看到unsafe.Pointer和uintptr和指针的配合使用可以给我们对运行时的代码做一些非常灵活的操作。