为什么是坏主意? 主要是可能是会让 Go 的垃圾收集器崩溃。Go GC 会查看程序可见的每个指针,以查看哪些内存仍在使用,以及哪些内存可以释放。如果它跟随的指针未指向有效的内存地址,则可能会崩溃。
让我们尝试一下,分配十亿个 unsafe.Pointers 并将他们全部设置为无效指针的值。
1
2
3
4
5
6
7
8
9
10
11
funcTestRandomUnsafePointers(t*testing.T){x:=make([]unsafe.Pointer,1e9)fori:=rangex{// Possible misuse of unsafe.Pointer? Definite misuse of unsafe.Pointer!
x[i]=unsafe.Pointer(uintptr(i*8))}runtime.GC()runtime.KeepAlive(x)}
funcTestRandomUnsafePointers2(t*testing.T){x:=make([]unsafe.Pointer,1e9)fori:=rangex{// Possible misuse of unsafe.Pointer? Definite misuse of unsafe.Pointer!
x[i]=unsafe.Pointer(uintptr(rand.Int64()))}runtime.GC()forrange10{fori:=rangex{// Possible misuse of unsafe.Pointer? Definite misuse of unsafe.Pointer!
x[i]=unsafe.Add(x[i],3)}runtime.GC()}runtime.KeepAlive(x)}
仍旧未崩溃。
如果我们不够聪明怎么办?
Go 可能会查看这些值并思考「啊嘞」,然后忽略他。 Go 可以与 C 交互,因此它需要能够处理在其控制之外分配的内存。多年来,我还使用 Go 直接通过系统调用分配的内存,没有任何问题(祈祷)。
如果指针的值看起来像它应该关心的内存,Go 可能会发现它更困难。如果我们存储的值曾经是 Go 本身分配的有效内存地址,但我们知道它不再有效怎么办?
这里我们分配一个足够大的切片,以便始终分配在堆上。然后,我们获取支持该切片的数组的地址,并将其放入 uintptr 中。我们知道 Go 不会将 uintptr 视为指针,因此将值保存在 uintptr 中不应导致 Go 保留分配。