unsafe黑魔法
前言
大家应该都知道 Go 语言是不支持指针的运算和转换
的。这是为什么呢?
由于 Go 语言是一门静态语言(也叫强类型语言),所以所有的变量都必须为标量类型。不同类型之间不能够进行赋值、计算等跨类型的操作,比方说一个字符串不能赋值给一个 int 类型的变量,一个字符串也不能跟一个整数相加、相减等操作,一个字符串也不能强制转换为整数类型。由于指针也有其对应的类型,所以也在 Compile 的静态类型检查的范围内。同时,静态类型又叫强类型,意思就是这个变量一旦定义其类型,便不能再更改它。
像下面这种,就是不对的
number := 5
numberPointer := &number
floatNumber := (*float32)(numberPointer)
fmt.Println(floatNumber)
编译器会报错:
# command-line-arguments
...: cannot convert numberPointer (type *int) to type *float32
直接翻译就知道这是为什么会报错吧
unsafe 包
用它之前,我们要知道:
- 它是围绕 Go 程序内存安全及类型的操作
- 用它写出来的程序很可能是不可移植的
- 不受 Go 1兼容性指南保护
unsafe.Pointer
type ArbitraryType int
type Pointer *ArbitraryType
ArbitraryType 仅用于来表示任意 Go 表达式类型,就是用来表示任意
的含义,他并不属于 unsafe 包的一部分。
所以 Pointer 代表人意类型的指针,类似于 C 语言里面的 void*。
Pointer
有四个核心操作:
- 任何类型的指针值都可以转换为 Pointer
- Pointer 可以转换为任何类型的指针值
- uintptr 可以转换为 Pointer
- Pointer 可以转换为 uintptr
number := 5
numberPointer := &number
floatNumber := (*float32)(unsafe.Pointer(numberPointer))
fmt.Println(floatNumber)
这样写就不报错了。拿 unsafe.Pointer 作为中间的桥梁,巧妙地将 int 类型的 number 转换为 float32 类型的 floatNumber.
Offsetof(偏移量)
type N struct{
i string
j int
}
func main(){
n := N{
i: "example",
j: 1
}
niPointer := unsafe.Pointer(&n)
*niPointer = "鱼"
njPointer := (*int)(unsafe.Pointer(uintptr(nPointer) + unsafe.Offsetof(n.j)))
*njPointer = 2
fmt.Printf("n.i: %s, n.j: %d", n.i, n.j)
}
输出结果:
n.i: 鱼, n.j: 2
分析这段代码做了什么事之前,我们需要知道关于结构体的一些概念:
- 结构体成员变量在内存存储上是一块连续的内存
- 结构体的初始地址就是第一个成员变量的内存地址
- 基于结构体的成员地址去计算偏移量,就能够计算出其他成员变量的内存地址
关于 unitptr :
- 是 Go 的内置类型。返回无符号整数,可存储一个完整的地址。
type uintptr uinptr
关于 offsetof :
func Offsetof(x ArbitraryType) uintptr
- 计算偏移量大小,传入任意类型参数,返回返回x表示的字段结构中的偏移量,该字符必须是 structValue.field 形式,换句话说,它返回
struct 的开头
和字段的开头
之间的字节数。
这个示例就是先将整个结构体的 unsafe.Pointer 转换为 unsafe.uintptr 然后将得到的 unsafe.uintptr 加上某个字段的偏移量 转换成 该字段其他类型的指针值
从而巧妙地将结构体某个字段 转换为 其他类型的指针值
可以知道 uintptr
类型是可以做指针运算的
注意
uintptr
类型是不能存储在临时变量中的。因为从 GC 角度来看,uintptr
类型的临时变量只是一个无符号整数,并不知道它是一个指针地址。因此当满足一定条件后,这个临时变量很可能会被垃圾回收掉,那么接下来的内存操作岂不成谜?
总结
1⃣️ unsafe.Pointer
可以让你的变量在不同的指针类型之间转来转去,就是可以表示成任何可循知道指针类型。
2⃣️ uintptr
常用于与 unsafe.Pointer
互相配合做指针运算。
3⃣️ unsafe 包实现了 Go 语言做指针运算和指针类型之间转换