uintptr是整数类型,不能直接解引用;必须经unsafe.Pointer中转才能转为指针,且需确保内存合法存活、对齐正确、生命周期可控,仅限CGO等极少数场景使用。

uintptr 不能直接解引用,别把它当指针用
Go 里 uintptr 是整数类型,不是指针类型。它只存地址数值,不参与 Go 的内存管理(没 GC 关联、不随栈移动),所以你不能写 *ptr 或 ptr.x 这类操作——编译器直接报错 invalid operation: *ptr (mismatched types *T and uintptr)。
常见错误场景:想“偏移结构体字段”或“从 C 指针还原 Go 对象”,结果直接对 uintptr 加减后尝试解引用。
- 必须先转回具体指针类型,比如
(*int)(unsafe.Pointer(uintptrVal)) - 转换前务必确认该地址确实指向合法、存活、未被回收的内存
- 如果原内存是 Go 分配的(比如切片底层数组),且后续有 GC 或栈复制,
uintptr很快就变野指针
用 unsafe.Pointer 做中转,别跳过这一步
uintptr 和指针互转必须经过 unsafe.Pointer,这是 Go 类型系统强制的安全闸门。绕过它(比如用 reflect.ValueOf(&x).UnsafeAddr() 得到 uintptr 后直接强转)看似能跑,但极可能在 GC 后崩溃或读到脏数据。
典型正确链路:&structField → unsafe.Pointer → uintptr → +offset → unsafe.Pointer → *T。
立即学习“go语言免费学习笔记(深入)”;
- 每次加减偏移后,都要用
unsafe.Pointer(uintptrVal)转回来才能再转目标指针 - 不要缓存中间的
uintptr值超过单次计算周期——尤其别存在全局变量或返回给调用方 - 注意对齐:结构体字段偏移得用
unsafe.Offsetof,别手算;uintptr加减字节数时,确保没越界
和 C 交互时,C 函数返回的指针要立刻转,别留 uintptr
C 代码返回的 void* 经 C.xxx() 调用后,在 Go 侧是 unsafe.Pointer。如果转成 uintptr 存着,下次再转回来用,大概率出问题——因为 Go 可能在两次调用间触发 GC,导致 C 分配的内存被 Go 认为“不可达”而干扰(哪怕 C 内存本不该被 GC 管)。
真实踩坑点:封装 C 库时,把 uintptr 当句柄存在 struct 里,之后方法里反复转回指针。
- 正确做法:C 返回的指针,立即转成 Go 指针(如
*C.struct_foo)或至少保持为unsafe.Pointer - 若必须存为整数(比如传给 syscall),用
uintptr(unsafe.Pointer(cPtr)),但每次使用前都重新转回unsafe.Pointer,再转具体类型 - 对 C 分配的内存,记得配对调用
C.free,别依赖 Go GC
性能没优势,纯为兼容或极端场景
有人以为用 uintptr 算地址比切片索引快,其实没有。现代 Go 编译器对 []byte 和 string 的下标访问做了大量优化,而 uintptr 运算要经历多次类型转换、潜在的越界风险检查(取决于上下文),实际更重。
真正需要它的场景非常窄:写 CGO 封装、实现自定义内存池、调试运行时、或者 patch 某些底层结构(比如修改 reflect.StringHeader 字段——虽然不推荐)。
- 99% 的业务逻辑不需要碰
uintptr,用切片、map、struct 字段访问更安全、可读、可维护 - 一旦用了,就得全程自己负责生命周期、对齐、符号表一致性(比如结构体字段顺序变更会破坏偏移计算)
- 交叉编译时注意指针宽度差异:
uintptr在 32 位和 64 位平台长度不同,硬编码偏移值会失效
最麻烦的不是语法怎么写,而是你永远得盯着那块内存——谁分配的、谁释放的、GC 会不会动它、并发时有没有竞态。这些事,uintptr 一个字都不会提醒你。










