golang unsafe.Pointer和uintptr

本文深入探讨了Go语言中unsafe.Pointer的特性和使用规范,包括其与uintptr的区别,如何进行合法转换,以及在系统调用、反射等场景下的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

uintptr

  • 一个足够大的无符号整型, 用来表示任意地址。
  • 可以进行数值计算。

unsafe.Pointer

  • 一个可以指向任意类型的指针
  • 不可以进行数值计算。
  • 有四种区别于其他类型的特殊操作:
    1. 任意类型的指针值均可转换为 Pointer。
    2. Pointer 均可转换为任意类型的指针值。
    3. uintptr 均可转换为 Pointer。
    4. Pointer 均可转换为 uintptr。

以下示范了unsafe.Pointer类型合法的操作,不符合以下规范的操作现在或者将来都很可能是非法的操作。

1. 把一个任意 *T1 转换为 Pointer *T

假设T2不大于T1, 且两者享有相同的内存布局。
例:

func Float64bits(f float64) uint64 {
	  return *(*uint64)(unsafe.Pointer(&f))
}

2. 把一个 Pointer类型转换为 uintptr, 但不转换回Pointer类型

把一个Pointer类型转换为uintptr类型,就是把所指向对象的内存地址保存到了uintptr类型中。 通常,这样的uintptr类型值只是用来打印调试。

一个uintptr是一个整数,不是一个引用。把一个Pointer转换为uintptr后,实际是剥离了原有的指针的语义,只取了地址的整数值。即使uintptr保存了某些对象的地址值,当该对象的内存地址发生变化时,GC并不会去更新uintprt的值, GC也还是可能会对对象进行垃圾回收。作为对比,在指针指向对象时,GC是不会对此对象进行垃圾回收的。


3. 把一个 Pointer类型转换为 uintptr,并且使用数值计算转换回Pointer类型

uintptr类型可以进行数值计算。 在分配了对象的情况, 可以将Pointer类型转换为uintptr类型,然后uintptr类型也可以通过数值计算, 偏移offset 的方式转换回Pointer类型。

p = unsafe.Pointer( uintptr ( p ) + offset )

有以下常见的用法:

// 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]))

但是, 与C语言不同,Go中无法使用指针偏移来指向未被事先分配的内存区域。

// INVALID: end points outside allocated space.
var s thing
end = unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Sizeof(s))

// INVALID: end points outside allocated space.
b := make([]byte, n)
end = unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(n))

注意: 这些转换都必须按照上述表达式的规范书写, 转换必须在一个表达式中完成,不能分开写。

以下是错误的写法:

// 错误。在转换操作前,不能将uintptr的值保存到临时变量中。
u := uintptr(p)
p = unsafe.Pointer(u + offset)

原因如下:

某些GC会把变量进行搬移来进行内存整理,这种类型的GC称为“移动的垃圾回收器”。当一个变量在内存中移动后,所有指向旧地址的指针都应该更新,并指向新地址。uintptr只是个数值,它的值不会变动。

上面的代码使得GC无法通过临时变量u了解它背后的指针。当转换执行的时候,p的位置可能已经发生了移动,这个时候u中的值就不再是原来p的地址了。

4. 使用syscall.Syscall 把一个Pointer类型转换为uintptr

Syscall函数存在于syscall包中,会直接把uintpr类型的参数直接传递给操作系统。根据调用方式的不同,可能中间会把uintptr当做指针来解释。也就是说,在某些情况下, 这种系统调用方式,会隐式的将uintptr类型转换到Pointer类型。

如果,要把一个指针作为参数传到syscalll函数中, 为了让编译器识别到特征, 必须在同一个表达式的参数列表中进行显式的转换。

//对的
syscall.Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(p)), uintptr(n))

//错的
u := uintptr(unsafe.Pointer(p))
syscall.Syscall(SYS_READ, uintptr(fd), u, uintptr(n))

5. 把 reflect.Value.Pointer 或 reflect.Value.UnsafeAddr的结果从uintptr类型转换为Pointer类型

reflect.Value.Pointer 或 reflect.Value.UnsafeAddr的结果返回的都是uintptr类型,而不是Pointer类型。

此举是为了禁止调用者在未提前import “unsafe”包的情况下就对结果进行任意类型的转换。 但是,这么做会导致结果很脆弱(因为是uintptr类型,无法在对象地址发生变化时更新值),所以应该在同一个表达式中立刻将结果转换为Pointer类型。

p := (*int)(unsafe.Pointer(reflect.ValueOf(new(int)).Pointer()))

同样,不能在转换前对unintptr类型的值进行存储。

// INVALID: uintptr cannot be stored in variable
// before conversion back to Pointer.
u := reflect.ValueOf(new(int)).Pointer()
p := (*int)(unsafe.Pointer(u))

6. 把 reflect.SliceHeader 或 reflect.StringHeader的data变量转换为/转换到一个Pointer

原因同上。

### Golang 中 `uintptr` 类型的使用场景及注意事项 #### 使用场景 `uintptr` 是 Go 语言中的无符号整数类型,用于表示指针值。尽管它不是真正的指针类型,但在某些情况下可以用来存储地址信息。 1. **跨包传递指针** 当需要在一个包中创建对象并将其传递给另一个包时,有时会遇到无法直接传递指针的情况。此时可以通过转换为 `uintptr` 来实现间接传递[^2]。 2. **底层操作** 对于一些涉及操作系统调用或硬件交互的功能模块,可能会涉及到内存地址的操作。例如,在网络编程中解析 UDP 地址时,内部可能需要用到 `ResolveUDPAddr` 函数来获取地址结构体,而该函数返回的结果中包含了与 `uintptr` 相关的信息[^1]。 3. **性能优化** 在特定场景下为了提高程序执行效率,比如减少垃圾回收器的压力或者避免不必要的拷贝动作,开发者可以选择将数据结构转化为 `uintptr` 进行快速比较或计算。 #### 注意事项 - **安全性风险** 将常规变量转成 `uintptr` 并再次恢复为原始类型的过程中存在潜在的安全隐患。由于绕过了编译期检查机制,一旦误操作可能导致不可预见的行为甚至崩溃。因此应当谨慎对待此类转换逻辑[^3]。 - **平台依赖性** 不同架构下的指针大小各异(如32位系统上通常是4字节长度而在64位环境下则是8字节),所以基于 `uintptr` 的代码往往具有一定的平台相关特性。编写这类代码前需充分考虑目标运行环境的特点。 - **GC不友好** 转换后的 `uintptr` 值不会被自动追踪管理,这意味着如果频繁地进行这样的变换,则容易引发内存泄漏等问题。对于长期存活的对象尤其需要注意这一点。 ```go // 示例:如何安全地使用 uintptr package main import "fmt" type MyStruct struct { value int } func (m *MyStruct) ToUintptr() uintptr { return uintptr(unsafe.Pointer(m)) } func FromUintptr(u uintptr) *MyStruct { return (*MyStruct)(unsafe.Pointer(u)) } func main() { ms := &MyStruct{value: 42} uptr := ms.ToUintptr() fmt.Printf("Original address as uintpttr: %d\n", uptr) restoredMs := FromUintptr(uptr) fmt.Printf("Restored object's value is: %d\n", restoredMs.value) } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值