本文将介绍下golang源码里经常出现的东西: unsafe.Pointer
和uintptr
工作中很少接触到这两个,但是平时如果看源码的话对这两个就不陌生了。所以还是有必要记录一下。
本文代码基于golang 1.18
Pointer
代表一个可以指向任何类型的指针,注意是任何。
golang源码对它的描述如下:
1
2
3
4
5
6
7
8
|
// 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.
// Pointer therefore allows a program to defeat the type system and read and write
// arbitrary memory. It should be used with extreme care.
|
简单总结下,Pointer
作为一个指针,可以无视golang强类型语言的特性,在任意的内存区域内读写值。这个特性很强,但大家在项目中最好不要随便使用,不然你猜他为什么放在unsafe(不安全)
包下
平常有四种关于Pointer
的常用操作:
- 任意的指针类型可以转换成
Pointer
- 一个
Pointer
可以转换成任意的指针类型
uintptr
可以转换成Pointer
Pointer
可以转换成uintptr
举个例子
1
2
3
4
5
6
7
8
9
10
11
12
|
func main() {
// a为int8的指针, b为int16的指针
a, b := new(int8), new(int16)
// 分别给a, b赋值
*a = 10
*b = 20
// output: 10, 20
fmt.Println(*a, *b)
// cannot use *b (variable of type int16) as type int8 in assignment
*a = *b
}
|
这里我们想将a指向b,但是编译器提示错误,原因就是它们两不是同一类型,将程序改下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
func main() {
// a为int8的指针, b为int16的指针
a, b := new(int8), new(int16)
// 分别给a, b赋值
*a = 10
*b = 20
// output: 10, 20
fmt.Println(*a, *b)
pb := unsafe.Pointer(b)
int8a := (*int8)(pb)
*a = *int8a
// output: 20, 20
fmt.Println(*a, *b)
}
|
例子中的转换流程:
b(type: *int16)
=> unsafe.Pointer
(任意的指针类型可以转换成Pointer
)
unsafe.Pointer
=> *int8
(一个Pointer
可以转换成任意的指针类型)
- 最后将a指向转换后的
*int8
地址,实现两种不同类型的转换
Pointer
的具体数据类型:
1
2
3
4
5
|
type Pointer *ArbitraryType
// ArbitraryType is here for the purposes of documentation only and is not actually
// part of the unsafe package. It represents the type of an arbitrary Go expression.
type ArbitraryType int
|
可以看到Pointer
是ArbitraryType
的指针类型,而后者本质上是int
类型,这里只是为了语义化,所以取了一个ArbitraryType(任意类型)
名字
golang中定义如下
1
2
3
|
// uintptr is an integer type that is large enough to hold the bit pattern of
// any pointer.
type uintptr uintptr
|
uintptr
本质上还是一个整形,只不过它能装载的范围很大,足以装载任何类型的指针。
这里需要注意它和unsafe.Pointer
的区别。前者保存了地址的整形,可以利用它对地址进行运算。因为它不实际持有引用,所以垃圾回收器可能会回收它所代表的内存区域。后者是个指针类型,可以指向任何区域。
举个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
type User struct {
Name string
Age int
}
func main() {
u := &User{}
// 将u的指针类型转换为unsafe.Pointer
uAddr := unsafe.Pointer(u)
// 计算出age字段的偏移量
ageOffset := unsafe.Offsetof(u.Age)
// 根据age字段的偏移量,得到age字段的具体内存地址
ageAddr := unsafe.Pointer(uintptr(uAddr) + ageOffset)
// 给age字段赋值
*(*int8)(ageAddr) = 20
// output: 20
fmt.Println(u.Age)
}
|
例子中的操作逻辑:
- 先申明一个User实例,并获取到类型为
unsafe.Pointer
的实例指针
- 调用
unsafe.Offsetof
方法获取到age字段距离结构体内存地址的偏移量(该方法传入一个结构体字段即可获得偏移量)
- 依据偏移量+user实例的内存地址,得到User.Age字段的内存地址
- 对该地址赋值
这个例子展示了 uintptr
可以转换成Pointer
和 Pointer
可以转换成uintptr
。 我们得到uintptr
之后就可以进行指针运算了。
本文从golang源码上介绍了unsafe.Pointer
和uintptr
两个结构,在golang源码中广泛用到了它们。算是看源码的前置知识了。
总结一下:
unsafe.Pointer
底层是一个整形的指针类型,可以读写内存中的任何一块区域,并且可以与任意类型互相转换(编译上不会报错)
uintptr
底层是一个整形,非指针类型。因为是非指针类型,所以它保存内存地址所对应的内存区域可能会被清除。它可以用于指针运算
特别注意:在项目中最好不要使用,因为你不能保证操作内存无误