Go语言大厂编程 unsafe.Pointer 如何在 runtime 时运用?

Go语言大厂编程 unsafe 不安全指针

将数据结构转成 unsafe.Pointer

func Unsafe1() {
   var m = map[int]int{
      2: 10086,
      3: 10087,
   }
   pointer := unsafe.Pointer(&m)
   fmt.Println(*(*map[int]int)(pointer))
}
复制代码

Output:

map[2:10086 3:10087]

这里将m转成unsafe.Pointer,之后我们手动将其转换((*map[int]int)(pointer)))回来,之后解除引用取出里面的内容。

遇到 interface 空接口类型的特殊性

func Unsafe2() {
   var m = map[int]int{
      2: 10086,
      3: 10087,
   }

   var i = func(v any) {//ps:笔者这里用1.18的版本,用了关键字`any`替换
      pointer := unsafe.Pointer(&v)
      fmt.Println(*(*map[int]int)(pointer))
   }
   i(&m)
}
复制代码

当你执行这段代码,并不会同 Unsafe1的代码一样正常,而是出现了异常报错信息 Output:

unexpected fault address 0x225d fatal error: fault [signal SIGSEGV: segmentation violation code=0x1 addr=0x225d pc=0x100d4c0]

goroutine 1 [running]: runtime.throw({0x10a1a5d?, 0x1741118?}) /Users/gre/Go/go1.18/src/runtime/panic.go:992 +0x71 fp=0xc000092a20 sp=0xc0000929f0 pc=0x102f411 runtime.sigpanic() ...

这个涉及了 interface 的类型转换逻辑 Interface接口设计 eface

简单来说就是在传递参数的过程中我们的结构进行了类型转换,所以我们没有办法直接转换回我们的数据。

那么我们应该怎么操作呢?

func Unsafe3() {
   var m = map[int]int{
      2: 10086,
      3: 10087,
   }

   var i = func(v any) {
      type eface struct {
         rtype unsafe.Pointer
         data  unsafe.Pointer
      }
      pointer := (*eface)(unsafe.Pointer(&v))
      fmt.Println(*(*map[int]int)(pointer.data))
   }
   i(&m)
}
复制代码

Output:

map[2:10086 3:10087]

我们定义了eface类型来接收类型和数据类型,在取出data转换我们需要的数据。

一般来说并不会让开发者去指定每一个数据的类型信息去解码,而是通过反射原本的结构体来实现。

func Unsafe4() {
   var m = map[int]int{
      2: 10086,
      3: 10087,
   }
   var (
      pointer  = unsafe.Pointer(&m)
      value1   = reflect.ValueOf(&m)
      ptrRType = *(*unsafe.Pointer)(unsafe.Pointer(&value1))
   )

   var packTAny = func(rtype unsafe.Pointer, data unsafe.Pointer) any {
      type eface struct {
         rtype unsafe.Pointer
         data  unsafe.Pointer
      }
      var i interface{}
      e := (*eface)(unsafe.Pointer(&i))
      e.rtype = rtype
      e.data = data
      return i
   }

   face := packTAny(ptrRType, pointer)
   m2 := (face).(*map[int]int)
   fmt.Println(*m2)
}
复制代码

Output:

map[2:10086 3:10087]

runtime 运行时Go程序是如何解析结构体的

//go:noescape
//go:linkname mapiterinit reflect.mapiterinit
func mapiterinit(rtype unsafe.Pointer, m unsafe.Pointer, it *hiter)

//go:noescape
//go:linkname mapiternext reflect.mapiternext
func mapiternext(it *hiter)

func Unsafe5() {
   var m = map[int]int{
      2: 10086,
      3: 10087,
   }
   var (
      value    = *(*unsafe.Pointer)(unsafe.Pointer(&m))
      value1   = reflect.ValueOf(m)
      ptrRType = *(*unsafe.Pointer)(unsafe.Pointer(&value1))
      it       hiter
   )

   mapiterinit(ptrRType, value, &it)
   for {
      key := it.key
      elem := it.value
      if key == nil {
         return
      }
      fmt.Println(*((*int64)(key)))
      fmt.Println(*((*int64)(elem)))
      mapiternext(&it)
   }
}
复制代码

Output:

map[2:10086 3:10087] 2 10086 3 10087

我们需要用 go:linkname 来关联到 runtime.mapmapiterinit 函数,在编译时能够引入该代码并执行。

你可以在这里学习 reflect 和 unsafe 的一些其它概念知识

  1. reflect(官方的 src/reflect 库代码)
  2. reflect2(号称在go中序列化 json 最快的库 json-iterator/go 引用的反射库)

猜你喜欢

转载自juejin.im/post/7087071105835335716