unsafe.Pointer
unsafe.Pointer
是一种特殊的指针类型。它可以指向任意类型的数据。你可以把它看作是 C 语言中的 void*
,但功能更受限,主要用于类型转换。unsafe.Pointer
会绕过 Go 的类型安全检查。编译器不会阻止你将一个 *int
转换为 unsafe.Pointer
,然后再转换为 *float64
。这非常危险,因为对转换后的指针进行操作可能导致内存访问错误或不可预测的行为。unsafe.Pointer
是一个真正的指针。Go 的垃圾回收器能够识别 unsafe.Pointer
,并知道它指向了一个内存对象。只要有一个 unsafe.Pointer
(或者从它转换而来的其他 Go 指针) 引用着某个对象,GC 就不会回收该对象。这是它与 uintptr
最核心的区别之一。uintptr
之间进行转换。reflect
包内部就使用了 unsafe
。unsafe.Pointer
做 ptr + 1
操作。uintptr
uintptr
是一个整数类型(无符号整型),它足够大,能够存储任何指针的位模式(内存地址的数值表示)。在 32 位系统上通常是 uint32
,在 64 位系统上通常是 uint64
。uintptr
本身只是一个整数。它不携带任何类型信息。你可以对它进行标准的整数算术运算(加、减等)。uintptr
只是一个普通的整数值。Go 的垃圾回收器不认为 uintptr
指向任何内存对象。即使一个 uintptr
变量存储了某个对象的地址,如果没有任何真正的指针(如 *T
或 unsafe.Pointer
)指向该对象,GC 仍然可能会回收那个对象。这是使用 uintptr
最需要注意的风险点。unsafe.Pointer
进行转换)。例如,计算结构体中字段的偏移量。uintptr
,如果在没有其他指针引用的情况下,仅通过 uintptr
持有地址并稍后尝试转换回指针来访问内存,可能会访问到已经被回收或重用的内存,导致程序崩溃或数据损坏。特性 | unsafe.Pointer |
uintptr |
---|---|---|
本质类型 | 指针 (*ArbitraryType ) |
整数 (uint ) |
GC 感知 | 是 (GC 知道它指向对象,阻止回收) | 否 (GC 视其为普通整数,不阻止回收) |
类型信息 | 无 (但本质是指针) | 无 |
算术运算 | 否 (不能直接加减) | 是 (可以进行整数加减) |
解引用 | 否 (需先转为具体类型指针) | 否 (本身是整数,无法解引用) |
主要用途 | 指针类型转换、与 uintptr 互转 |
指针算术运算 (配合 unsafe.Pointer )、地址数值化 |
安全性 | 不安全 (绕过类型系统) | 本身是整数,但与指针转换使用时极不安全 |
如果你需要访问某个指针偏移 N 个字节的位置,通常的模式是:
package main
import (
"fmt"
"unsafe"
)
type MyStruct struct {
A int32 // 4 bytes
B int64 // 8 bytes
}
func main() {
s := MyStruct{A: 1, B: 2}
sPtr := &s // 1. 获取原始指针 (*MyStruct)
// 假设我们要访问字段 B,需要跳过字段 A (4 bytes)
offset := unsafe.Offsetof(s.B) // 通常使用 unsafe.Offsetof 获取偏移量更安全
// 或者手动计算:offset := unsafe.Sizeof(s.A)
// 2. 将原始指针转换为 unsafe.Pointer (GC 仍然知道 sPtr 指向 s)
unsafeSPtr := unsafe.Pointer(sPtr)
// 3. 将 unsafe.Pointer 转换为 uintptr 以进行算术运算
uintptrSPtr := uintptr(unsafeSPtr)
// 4. 对 uintptr 执行算术运算 (加上偏移量)
uintptrBPtr := uintptrSPtr + offset // 注意:offset 必须是 uintptr 类型
// 5. 将计算后的 uintptr 转换回 unsafe.Pointer
unsafeBPtr := unsafe.Pointer(uintptrBPtr)
// 6. 将 unsafe.Pointer 转换为目标类型的指针 (*int64)
bPtr := (*int64)(unsafeBPtr)
// 7. 现在可以通过 bPtr 访问字段 B 的值了
fmt.Println("Value of B:", *bPtr) // 输出: Value of B: 2
// 修改 B 的值
*bPtr = 99
fmt.Println("New value of B in s:", s.B) // 输出: New value of B in s: 99
}
关键点: 在第 3 步到第 5 步之间,uintptrSPtr
和 uintptrBPtr
只是整数。如果在这期间,原始的 sPtr
或 unsafeSPtr
因为某些原因不再被引用(例如离开了作用域且没有其他引用),理论上 GC 可能回收 s
指向的内存。这就是为什么 unsafe
操作必须非常小心,并且转换链条通常需要在一个表达式或很小的代码块内完成,确保原始指针在整个过程中是活跃的。
总结: unsafe.Pointer
是连接 Go 安全世界和底层内存操作的桥梁,它本身是指针并被 GC 跟踪。uintptr
只是存储地址数值的整数,用于算术运算,但 GC 不关心它。两者经常配合使用,但必须极其谨慎,深刻理解其对内存安全和 GC 的影响。大部分情况下,应避免使用 unsafe
包。
如果您喜欢我的文章,请点击下面按钮随意打赏,您的支持是我最大的动力。
最新评论