©
本文档使用
php.cn手册 发布
import "unsafe"
概观
索引
软件打包不安全包含围绕 Go 程序类型安全的操作。
导入不安全的程序包可能不可移植,并且不受 Go 1 兼容性准则的保护。
func Alignof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Sizeof(x ArbitraryType) uintptr
type ArbitraryType
type Pointer
unsafe.go
func Alignof(x ArbitraryType) uintptr
Alignof 接受任何类型的表达式 x 并返回假设变量 v所需的对齐,就像 v 通过 var v = x 声明一样。它是最大的值 m,使得 v 的地址总是为零mod m。它与 reflect.TypeOf(x).Align() 返回的值相同。作为一种特殊情况,如果变量s是结构类型,并且 f 是该结构中的字段,那么 Alignof(s.f) 将返回结构中该类型字段所需的对齐。这种情况与 reflect.TypeOf(s.f).FieldAlign() 返回的值相同。
func Offsetof(x ArbitraryType) uintptr
Offsetof 返回由 x 表示的字段结构中的偏移量,它必须是 structValue.field 的形式。换句话说,它返回结构开始和字段开始之间的字节数。
func Sizeof(x ArbitraryType) uintptr
Sizeof 采用任何类型的表达式x并返回假设变量 v 的字节大小,就像 v 通过 var v = x 声明一样。该大小不包括可能由 x 引用的任何内存。例如,如果 x 是切片,则 Sizeof 返回切片描述符的大小,而不是切片引用的内存大小。
ArbitraryType 仅用于文档目的,实际上并不是不安全软件包的一部分。它表示任意 Go 表达式的类型。
type ArbitraryType int
指针表示指向任意类型的指针。类型指针有四种可用于其他类型的特殊操作:
- A pointer value of any type can be converted to a Pointer.- A Pointer can be converted to a pointer value of any type.- A uintptr can be converted to a Pointer.- A Pointer can be converted to a uintptr.
指针因此允许程序打败类型系统并读写任意内存。应该非常小心地使用它。
涉及指针的以下模式是有效的。不使用这些模式的代码今天可能无效,或在将来变得无效。即使下面的有效模式也有重要的注意事项。
运行“go vet”可以帮助找到不符合这些模式的指针的使用,但是从“go vet”沉默并不能保证代码有效。
(1)将 * T1 转换为 * T2 的指针。
假设 T2 不大于 T1 并且两者共享相同的存储器布局,则该转换允许将一种类型的数据重新解释为另一种类型的数据。一个例子是 math.Float64bits 的实现:
func Float64bits(f float64) uint64 {return *(*uint64)(unsafe.Pointer(&f))}
(2)将指针转换为 uintptr(但不返回到指针)。
将指针转换为 uintptr 会以整数形式生成指向的内存地址。这种 uintptr 通常用于打印它。
将 uintptr 转换回指针通常无效。
uintptr 是一个整数,而不是引用。将指针转换为 uintptr 将创建不带指针语义的整数值。即使 uintptr 持有某个对象的地址,如果对象移动,垃圾收集器也不会更新该 uintptr 的值,uintptr 也不会使该对象不被回收。
其余模式枚举从 uintptr 到指针的唯一有效转换。
(3)用算术将指针转换为 uintptr 并返回。
如果 p 指向已分配的对象,则可以通过转换为 uintptr,添加偏移量以及转换回指针来将对象前进。
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 中不同的是,仅仅在它的原始分配结束时超前一个指针是无效的:
// 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))
请注意,这两个转换必须出现在相同的表达式中,只有它们之间的中间算术:
// INVALID: uintptr cannot be stored in variable// before conversion back to Pointer.u := uintptr(p)p = unsafe.Pointer(u + offset)
(4)在调用 syscall.Syscall 时将指针转换为 uintptr。
系统调用包中的 Syscall 函数直接将它们的 uintptr 参数传递给操作系统,然后根据调用的细节,将其中的一些重新解释为指针。也就是说,系统调用实现隐式地将某些参数从 uintptr 转换回指针。
如果必须将指针参数转换为 uintptr 以用作参数,则该转换必须出现在调用表达式本身中:
syscall.Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(p)), uintptr(n))
编译器处理一个转换为 uintptr 的指针,转换为在汇编中实现的函数的调用的参数列表中,通过安排被引用的已分配对象(如果有的话)被保留,直到调用完成时才移动,即使仅从类型在通话过程中将显示该对象不再需要。
为了让编译器识别这种模式,转换必须出现在参数列表中:
// INVALID: uintptr cannot be stored in variable// before implicit conversion back to Pointer during system call.u := uintptr(unsafe.Pointer(p))syscall.Syscall(SYS_READ, uintptr(fd), u, uintptr(n))
(5)将 reflect.Value.Pointer 或 reflect.Value.UnsafeAddr 的结果从 uintptr 转换为指针。
Package 反射的 Value 方法名为 Pointer 和 UnsafeAddr,返回类型为 uintptr 而不是 unsafe.Pointer,以防止调用者将结果更改为任意类型,而不首先导入“unsafe”。但是,这意味着结果很脆弱,必须在调用后立即转换为指针,并使用相同的表达式:
p := (*int)(unsafe.Pointer(reflect.ValueOf(new(int)).Pointer()))
与上述情况一样,在转换之前存储结果无效:
// INVALID: uintptr cannot be stored in variable// before conversion back to Pointer.u := reflect.ValueOf(new(int)).Pointer()p := (*int)(unsafe.Pointer(u))
(6)将 Reflection.SliceHeader 或 reflect.StringHeader 数据字段转换为指针或从指针转换而来。
与前面的情况一样,反射数据结构 SliceHeader 和 StringHeader 将字段 Data 声明为 uintptr,以防止调用者在不首先导入“不安全”的情况下将结果更改为任意类型。但是,这意味着 SliceHeader 和 StringHeader 仅在解释实际切片或字符串值的内容时有效。
var s string hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) // case 1hdr.Data = uintptr(unsafe.Pointer(p)) // case 6 (this case)hdr.Len = n
在这种用法中,hdr.Data 实际上是一种替代方法来引用片头中的基础指针,而不是 uintptr 变量本身。
一般来说,reflect.SliceHeader 和 reflect.StringHeader 只能用作 * reflect.SliceHeader 和 * reflect.StringHeader 指向实际的切片或字符串,而不能用作普通结构。程序不应该声明或分配这些结构类型的变量。
// INVALID: a directly-declared header will not hold Data as a reference.var hdr reflect.StringHeader hdr.Data = uintptr(unsafe.Pointer(p))hdr.Len = n s := *(*string)(unsafe.Pointer(&hdr)) // p possibly already lost
type Pointer *ArbitraryType