非常教程

Go参考手册

不安全性 | unsafe

不安全性 | 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

文件打包

unsafe.go

func Alignof(查看源文档)

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(查看源代码)

func Offsetof(x ArbitraryType) uintptr

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

func Sizeof(查看源代码)

func Sizeof(x ArbitraryType) uintptr

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

type ArbitraryType(查看源代码)

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

type ArbitraryType int

type Pointer(查看源代码)

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

- 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 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 指向实际的片或字符串,而不能用作普通结构。程序不应该声明或分配这些结构类型的变量。

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

不安全性 | unsafe相关

Go

Go 是一种编译型语言,它结合了解释型语言的游刃有余,动态类型语言的开发效率,以及静态类型的安全性。它也打算成为现代的,支持网络与多核计算的语言。要满足这些目标,需要解决一些语言上的问题:一个富有表达能力但轻量级的类型系统,并发与垃圾回收机制,严格的依赖规范等等。这些无法通过库或工具解决好,因此Go也就应运而生了。

主页 https://golang.org/
源码 https://go.googlesource.com/go
发布版本 1.9.2

Go目录

1.档案 | archive
2.缓冲区 | bufio
3.内置 | builtin
4.字节 | bytes
5.压缩 | compress
6.容器 | container
7.上下文 | context
8.加密 | crypto
9.数据库 | database
10.调试 | debug
11.编码 | encoding
12.错误 | errors
13. expvar
14.flag
15. fmt
16. go
17.散列 | hash
18.html
19.图像 | image
20.索引 | index
21.io
22.日志 | log
23.数学 | math
24. math/big
25.math/bits
26.math/cmplx
27.math/rand
28.拟态 | mime
29.net
30.net/http
31. net/mail
32. net/rpc
33.net/smtp
34. net/textproto
35. net/url
36.os
37.路径 | path
38.插件 | plugin
39.反射 | reflect
40.正则表达式 | regexp
41.运行时 | runtime
42.排序算法 | sort
43.转换 | strconv
44.字符串 | strings
45.同步 | sync
46.系统调用 | syscall
47.测试 | testing
48.文本 | text
49.时间戳 | time
50.unicode
51.不安全性 | unsafe
52.Go 语言数据类型
53.Go 语言基础语法
54.Go 语言结构
55.Go 语言 select 语句
56.Go 语言 switch 语句
57.Go 语言 if 语句嵌套
58.Go 语言 if…else 语句
59.Go 语言 if 语句
60.Go 语言运算符
61.Go 语言常量
62.Go 语言函数闭包
63.Go 语言函数作为实参
64.Go 语言函数引用传递值
65.Go 语言函数值传递值
66.Go 语言函数
67.Go 语言 goto 语句
68.Go 语言 continue 语句
69.Go 语言 break 语句
70.Go 语言循环嵌套
71.Go 语言 for 循环
72.Go 语言结构体
73.Go 语言指针作为函数参数
74.Go 语言指向指针的指针
75.Go 语言指针数组
76.Go 语言指针
77.Go 语言向函数传递数组
78.Go 语言多维数组
79.Go 语言变量作用域
80.Go 语言函数方法
81.Go 错误处理
82.Go 语言接口
83.Go 语言类型转换
84.Go 语言递归函数
85.Go 语言Map(集合)
86.Go 语言范围(Range)
87.Go 语言切片(Slice)
88.Go 并发
89.Go fmt.Sprintf 格式化字符串