You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// 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
The text was updated successfully, but these errors were encountered: