Overview

unsafe包含了绕过go类型安全的方法,导入unsafe包可能是不可移植的.

type ArbitraryType


type ArbitraryType int

ArbitraryType在包中的目的仅仅是为了文档化且实际上不是unsafe包中的一部分,它展示了Go中任意表达式的类型.

Functions

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).FieldAlign()相同,AlignOf返回值是Go的常量.

func Offsetof(x ArbitraryType) uintptr

OffsetOf返回x中结构体字段的偏移,那个必须是structValue.field的形式,此外,它返回结构体起始到字段起始的字节数,OffsetOf的返回值是Go的常量.

func Sizeof(x ArbitraryType) uintptr

SizeOf取出x的任意类型,并返回其假设变量v像通过var v = x声明的字节数,大小不包含任何可能被x引用的内存,比如x是切片,SizeOf返回slice描述符的大小,而不是其引用的内存,SizeOf返回值是Go的常量.

type Pointer


type Pointer *ArbitraryType
Pointer表示任意类型的指针,有四种操作对于Pointer类型提供而其他类型不提供的.

任何类型值的指针都可以被转换为Pointer

Pointer能够被转换为任何类型的指针值

uintptr能够被转化为Pointer

Pointer能够被转换为uintptr

Pointer因此允许击败类型系统并且读和写任意的内存,使用它应该特别的小心,以下涉及的Pointer模式是合理的,不使用这些模式的代码很可能今天或未来不合理.甚至下面的有效模式也带有重要的警告. 运行"go vet"能够帮助发现使用Pointer不是通过这些模式,但是"go vet"沉默不代表代码是合理的.

转换*T1到Pointer到*T2

前提是T2不大于T1且T1,T2有相同的内存布局,这个转换允许解释一种类型的数据为另一中类型的数据,一个实现是math.Float64bits:
func Float64bits(f float64) uint64 {
    return *(*uint64)(unsafe.Pointer(&f))
}

转换为uintptr(但不会转回为Pointer)

转换Pointer到uintptr产生值指向的内存地址,这种uintptr的用法通常是打印它.转换一个uintptr回Pointer通常是不合理的,uintptr是一个整型,不是一个引用,转换一个Pointer到uintptr会创建一个非指针语义的整型值,即使uintptr掌握一些对象的地址,垃圾收集器将不会更新uintptr的值当对象移动了,uintptr也不会使该对象被回收. 剩余的模式列举了从uintptr到Pointer唯一有效转换.

转换一个Pointer到uintptr或反过来,使用算术

如果p指向一个已分配的对象,则可以通过将对象转换为uintptr并附加偏移值转换回Pointer
p = unsafe.Pointer(uintptr(p) + offset)

最多使用该模式的共同点是访问结构体的字段或数组的元素

// 等价于 f := unsafe.Pointer(&s.f)
f := unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f))
// 等价于 e := unsafe.Pointer(&x[i])
e := unsafe.Pointer(uintptr(unsafe.Pointer(&x[0])) + i * unsafe.Sizeof(x[0]))

通过这种方式地址减或加一个偏移是合理的,使用&^舍入地址也是合理的,通常是为了对齐,在所有的情况下,结果必须继续指向原来对象的内部.
不像C语言,递进指针超过其原始分配的结尾是不合理的.

// 不合理: end超过分配空间的结尾
var s thing
end := unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Sizeof(s))
// 不合理: end超过分配空间的结尾
b := make([]byte, n)
end := unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(n))

请注意两种转换必须出现在相同的表达式,只介于二者的算术:

// 不合理: uintptr在转为Pointer前不能储存在变量中
u := uintptr(p)
p = unsafe.Pointer(u + offset)

注意指针必须是指向已分配的对象,所以它可能是nil

// 不合理: 转换nil pointer
u := unsafe.Pointer(nil)
p := unsafe.Pointer(uintptr(u) + offset)

当调用syscall.Syscall,转换Pointer到uintptr

syscall包Syscall函数直接传递uintptr参数到系统调用,那个取决于调用的细节,当作指针重新解释,系统调用实现不是明确的转换uintptr到指针.如果一个指针参数必须转换为uintptr,转换必须出现在调用表达式本身.
syscall.Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(p)), uintptr(n))

编译器通过安排引用已分配的的对象来处理在汇编中实现的函数的调用参数列表中uintptr转换为Pointer,如果有的话,它不会被移动直到调用完成,即使仅从类型来看,在调用过程中似乎不再需要该对象. 对于编译器认识这种模式,转换必须出现在参数列表中.

// 不合理: uintptr不能储存在变量中隐式转换为Pointer之前在系统调用期间
u := uintptr(unsafe.Pointer(p))
syscall.Syscall(SYS_READ, uintptr(fd), u, uintptr(n))

转换reflect.Value.Pointer或reflect.Value.UnsafeAddr的结果uintptr到Pointer

包reflect.Value的方法reflect.Value.Pointer和reflect.Value.UnsafeAddr返回uintptr而不是Pointer,是为了保证调用将其转化为任意类型首先导入"unsafe"包,这意味着调用的结果是脆弱的,调用者必须在调用后立即转为Pointer在同样的表达式中.
p := (*int)(unsafe.Pointer(reflect.ValueOf(new (int).Pointer))

与上述情况一样,储存其结果是不合理的

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

转换reflect.SliceHeader或reflect.StringHeader.Data字段到Pointer

在以前的情况下,reflect的数据结构SliceHeader和StringHeader声明的Data字段当作uintptr保证调用者转为任意类型的指针前导入"unsafe"包.这意味着SliceHeader和StringHeader只有当解释实际的切片或字符串值才合理
var s string
hdr = (*reflect.StringHeader)(unsafe.Pointer(&s)) // 情况1
hdr.Data = uintptr(unsafe.Pointer(p))
hdr.Len = n

在这种用法中,hdr.Data是一种可选的方法去指出在string header的底层指针而不是uintptr本身. 通常,reflect.SliceHeader,reflect.StringHeader应该只使用*reflect.SliceHeader,*reflect.StringHeader指向实际的切片或字符串,不要当作普通的结构体,程序中不应该声明或分配这些结构体类型的变量.

// 不合理: 直接声明的header将不会持有Data的引用
var hdr reflect.SliceHeader
hdr.Data = uintptr(unsafe.Pointer(p))
hdr.Len = n
s := *(*string)(unsafe.Pointer()) // p可能早已遗失