首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

不安全性 | unsafe

  • import "unsafe"
  • 概论
  • 索引

概论

软件包的不安全包括围绕 Go 程序类型安全的操作步骤。

导入不安全的程序包可能不可移植,并且不受 Go 1 兼容性准则的保护。

索引

  • func Alignof(x ArbitraryType) uintptr
  • func Offsetof(x ArbitraryType) uintptr
  • func Sizeof(x ArbitraryType) uintptr
  • type ArbitraryType
  • type Pointer

文件打包

代码语言:javascript
复制
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(查看源代码)

代码语言:javascript
复制
func Offsetof(x ArbitraryType) uintptr

Offsetof 返回由 x 表示的字段结构中的偏移量,它必须是 structValue.field 的形式。换句话说,它返回结构开始和字段开始之间的字节数。

func Sizeof(查看源代码)

代码语言:javascript
复制
func Sizeof(x ArbitraryType) uintptr

Sizeof 采用任何类型的表达式x并返回假设变量 v 的字节大小,就像 v 通过 var v = x 声明一样。该大小不包括可能由 x 引用的任何内存。例如,如果 x 是切片,则 Sizeof 返回切片描述符的大小,而不是切片引用的内存大小。

type ArbitraryType(查看源代码)

ArbitraryType 仅用于文档目的,实际上并不是不安全软件包的一部分。它表示任意 Go 表达式的类型。

代码语言:javascript
复制
type ArbitraryType int

type Pointer(查看源代码)

指针表示指向任意类型的指针。类型指针有四种特殊操作,这种类型指针不适用于其他类型。

代码语言:javascript
复制
- 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 的实现:

代码语言:javascript
复制
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,添加偏移量以及转换回指针来将对象前进。

代码语言:javascript
复制
p = unsafe.Pointer(uintptr(p) + offset)

这种模式最常见的用途是访问结构中的字段或数组中的元素:

代码语言:javascript
复制
// 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 中不同的是,仅仅在它的原始分配结束时超前一个指针是无效的:

代码语言:javascript
复制
// 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))

请注意,这两个转换必须出现在相同的表达式中,只有它们之间的中间算术:

代码语言:javascript
复制
// 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 以用作参数,则该转换必须出现在调用表达式本身中:

代码语言:javascript
复制
syscall.Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(p)), uintptr(n))

编译器处理一个转换为 uintptr 的指针,转换为在汇编中实现的函数的调用的参数列表中,通过安排被引用的已分配对象(如果有的话)被保留,直到调用完成时才移动,即使仅从类型在通话过程中将显示该对象不再需要。

为了让编译器识别这种模式,转换必须出现在参数列表中:

代码语言:javascript
复制
// 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”。但是,这意味着结果很脆弱,必须在调用后立即转换为指针,并使用相同的表达式:

代码语言:javascript
复制
p := (*int)(unsafe.Pointer(reflect.ValueOf(new(int)).Pointer()))

与上述情况一样,在转换之前存储结果无效:

代码语言:javascript
复制
// 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 仅在解释实际片或字符串值的内容时有效。

代码语言:javascript
复制
var s string
hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) // case 1
hdr.Data = uintptr(unsafe.Pointer(p))              // case 6 (this case)
hdr.Len = n

在这种用法中,hdr.Data 实际上是一种替代方法来引用片标题中的基础指针,而不是 uintptr 变量本身。

一般来说,reflect.SliceHeader 和 reflect.StringHeader 只能用作 * reflect.SliceHeader 和 * reflect.StringHeader 指向实际的片或字符串,而不能用作普通结构。程序不应该声明或分配这些结构类型的变量。

代码语言:javascript
复制
// 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
代码语言:javascript
复制
type Pointer *ArbitraryType

扫码关注腾讯云开发者

领取腾讯云代金券