go源码解读-unsafe包

unsafe包

  • unsafe包中的操作是不安全的操作,会绕过go原因的内存保护机制
  • 引用有风险,使用须谨慎
  • unsafe包提供了直接操作内存的能力

unsafe源码

  • unsafe包中只包含两个结构体和三个函数
1
2
3
4
5
type ArbitraryType int
type Pointer *ArbitraryType
func Sizeof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Alignof(x ArbitraryType) uintptr

两个变量

1
2
type ArbitraryType int
type Pointer *ArbitraryType
  • ArbitraryType实际上并不是unsafe包的一部分,只是为了文档展示
  • ArbitraryType虽然类型时int,但是其实质上代表的是任意类型的go表达式
    -
  • Pointer变量代表一个指向ArbitraryType对象的指针,实际上就是一个指向任意类型的指针
  • Pointer结构体有四个其他类型的结构体不具有的操作,这些能力使得go可以无视类型的限制来读写任意的内存,使用的时候需要谨慎
    • 一个任意结构体类型的指针可以转换为Pointer类型
    • 一个Pointer类型的结构体可以转换成一个指针
    • 一个uintptr可以转换成Pointer类型
    • 一个Pointer类型的结构体可以转换成一个uintptr指针,uintptr是一个能存储指针的整形值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
type Human struct {
sex bool
age uint8
min int
name string
}
func main() {
human := Human{
true,
30,
1,
"hello",
}
// 结构体转换
fmt.Printf("address os h is %p \n", &human)
pointer := unsafe.Pointer(&human)
fmt.Printf("convert pointer human to Pointer type: %p \n", pointer)
// 直接操作对象的内存
u1 := (*int16)(unsafe.Pointer(uintptr(unsafe.Pointer(&human)) + unsafe.Offsetof(human.age)))
fmt.Println(human.age)
*u1 = 12
fmt.Println(human.age)
}
/*
address os h is 0xc00000c060
convert pointer human to Pointer type: 0xc00000c060
0xc00000c061
30
12
*/
  • unsafe包规定了一些Pointer合理的使用模式,不符合的可能已经不可用或者即将不可用
    • 将*T1类型的指针转换成Pointer类型,然后再讲Pointer类型转换成T2类型指针
      • 这要求T1占用的内存要大于T2占用的内存
      • 如math.Float64bits的实现
    • 将一个Pointer类型转换成一个uintptr类型,也就是将指向的值的地址作为一个uintptr变量的值,
      • 将uintptr转换成Pointer通常来说会产生问题
      • uintptr是个值,不是指针,因此其没有任何语义
      • uintptr即使有某个对象的地址,但是在gc的时候是不会影响该对象的gc操作的
    • Pointer -> uintptr -> Pointer有时候是合法的,如进行算数运算的
      • p = unsafe.Pointer(uintptr(p)+offset)
      • 最常用的地方是获取结构体中的元素指针或者数组中的元素
      • f := unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f)) 等价于 f := unsafe.Pointer(&s.f)
      • e := unsafe.Pointer(uintptr(unsafe.Pointer(&x[0])) + i*unsafe.Sizeof(x[0])) 等价于 e := unsafe.Pointer(&x[i])
      • 但是如果计算完指向了未分配的内存,则是不合法的
    • 调用syscall.Syscall的时候将Pointer类型转换成uintptr
    • 将reflect.Value.Pointer或者reflect.Value.UnsafeAddr的结果从uintptr转换成Pointer类型时合法的
      • 这是因为reflect返回uintptr可以让上层调用不必引入unsafe包
      • p :+ (*int)(unsafe.Pointer(reflect.ValueOf(new)))
    • Pointer可以与reflect.SliceHeader或者reflect.StringHeader的Data字段进行互相转换
      • hdr.Data = uintptr(unsafe.Pointer(p))
1
2
3
4
// Float64bits returns the IEEE 754 binary representation of f,
// with the sign bit of f and the result in the same bit position,
// and Float64bits(Float64frombits(x)) == x.
func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) }

三个函数

1
2
3
func Sizeof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Alignof(x ArbitraryType) uintptr
  • 三个函数传入的参数为ArbitraryType类型变量,也就是可以穿任意类型的数据的地址进来
  • SizeOf函数返回x类型占据的字节数的
1
2
3
4
5
6
func main() {
slice := []int{2, 3, 4, 5, 6, 7, 8, 9}
i := int(6)
fmt.Println(int(unsafe.Sizeof(i)) * len(slice)) // 64
fmt.Println(unsafe.Sizeof(slice)) // 24
}
  • Offsetof实际上返回的是当前结构体开始的位置到当前字段的位置之间的偏移量
  • Alignof返回变量对其字节数量ß
Donate comment here