当前位置: 代码迷 >> 综合 >> golang unsafe.Pointer和uintptr
  详细解决方案

golang unsafe.Pointer和uintptr

热度:56   发布时间:2024-01-25 12:40:52.0

uintptr
一个足够大的无符号整型, 用来表示任意地址。
可以进行数值计算。
unsafe.Pointer
一个可以指向任意类型的指针。
不可以进行数值计算。
有四种区别于其他类型的特殊操作:
任意类型的指针值均可转换为 Pointer。
Pointer 均可转换为任意类型的指针值。
uintptr 均可转换为 Pointer。
Pointer 均可转换为 uintptr。

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

1. 把一个任意 *T1 转换为 Pointer *T
假设T2不大于T1, 且两者享有相同的内存布局。
例:

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

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]))
1
2
3
4
5

但是, 与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))

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

以下是错误的写法:

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

原因如下:
某些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))
1
2
3
4
5
6

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()))
1
同样,不能在转换前对unintptr类型的值进行存储。

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

6. 把 reflect.SliceHeader 或 reflect.StringHeader的data变量转换为/转换到一个Pointer
原因同上。
————————————————
版权声明:本文为CSDN博主「喝可乐_耍酒疯」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/hello_ufo/article/details/86713947

  相关解决方案