// equivalent to f := unsafe.Pointer(&s.f)
f := unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f))
// equivalent to e := unsafe.Pointer(&x[i])
e := unsafe.Pointer(uintptr(unsafe.Pointer(&x[0])) + i*unsafe.Sizeof(x[0]))
特别注意:因为上面说的原因,uintptr 不能放在临时变量内,所以下面这样分开使用也是无效的:
u := uintptr(p)
p = unsafe.Pointer(u + offset)
另外,做地址偏移的时候,要注意越界的问题,比如:
a = []int{0,1,2,3}
p := unsafe.Pointer(uintptr(unsafe.Pointer(&a)) + len(a) * unsafe.Sizeif(a[0]))
此时,变量 p 指向的地址是未知的,可能会出现不声明,直接偷偷的读写未知内存地址的情况,对系统运行稳定性影响很大。
// Assume p points to a sequence of byte and
// n is the number of bytes in the sequence.
var hdr reflect.StringHeader
hdr.Data = uintptr(unsafe.Pointer(new([5]byte)))
// Now the just allocated byte array has lose all
// references and it can be garbage collected now.
hdr.Len = 5
s := *(*string)(unsafe.Pointer(&hdr))
小结
在有些场景下,使用 unsafe.Poniter 可以帮助我们写出高效的代码,例如在 sync/atomic 包内的使用。而且一些底层或 C 调用,必须要用到 Poniter。
unsafe 包用于有经验的开发者绕过 Go 类型系统的安全性限制,一定要深入理解上面的六种使用场景,谨慎使用,否则很容易引起严重的内存问题,有经验的开发者都知道,这类问题通常是很难定位的,对系统的稳定性影响很大。
Go unsafe 包探究
unsafe
包的作用有两个:包接口比较简单,包括 3 个函数:
unsafe 函数都是在编译时计算返回结果的,所以,可以直接用于常量赋值,也要注意,尽量不要将运行时变量类型(例如 slice)作为这些函数的参数出入,可能会导致非预期的结果。
包括 2 种类型:
unsafe.Pointer
是本包的精华,也是被使用最多的功能点。Pointer 允许程序(开发者)跳脱 Go 的类型系统,(通过指针转换与运算)读写任意内存,所以要小心使用。 Pointer 总结下来就两个特性,也是实现前面说的目标(作用)的基础:uintptr
是无符号整型,被用来存放指针值(地址)。unsafe.Pointer
+uintptr
就能实现指针偏移计算了。因为uintptr
变量存放的是某个变量的地址,因此,uintpter
变量值对应的内存地址块(对应的变量)可能会被 GC 回收掉。unsafe.Pointer
本质上就是指针,该类型变量指向的内存块则不会被回收,因此,应该使用unsafe.Pointer
类型变量来保持变量地址不被回收。安全的使用场景
前面说过,使用 Pointer 必须要非常小心才行,官方定义了 6 种安全有效的使用场景。使用
go vet
工具可以检测出不符合这些场景的调用。*T1
转化成*T2
与 Pointer 不同,uintptr 保存的地址指向的变量是可以被 GC 回收的。
可以使用
runtime.KeepAlive
函数避免变量被 GC。通常用于访问结构体或者数组等:
特别注意:因为上面说的原因,uintptr 不能放在临时变量内,所以下面这样分开使用也是无效的:
另外,做地址偏移的时候,要注意越界的问题,比如:
此时,变量 p 指向的地址是未知的,可能会出现不声明,直接偷偷的读写未知内存地址的情况,对系统运行稳定性影响很大。
syscall.Syscall
时,将 Pointer 值转成 uintptrData
字段返回的也是 uintptr:reflect.SliceHeader
的使用:如果最后一行的 Printf 不存在的话,
runtime.KeepAlive
的调用是必须的。 另外,最好不要像下面这样,直接从 StringHeader 或 StringHeader 直接创建对象:小结
在有些场景下,使用
unsafe.Poniter
可以帮助我们写出高效的代码,例如在sync/atomic
包内的使用。而且一些底层或 C 调用,必须要用到 Poniter。unsafe 包用于有经验的开发者绕过 Go 类型系统的安全性限制,一定要深入理解上面的六种使用场景,谨慎使用,否则很容易引起严重的内存问题,有经验的开发者都知道,这类问题通常是很难定位的,对系统的稳定性影响很大。
参考
Go 101 : https://go101.org/article/unsafe.html