初探GO中unsafe包的使用

 更新时间:2023年08月27日 14:59:02   作者:今天捡到一百块钱  
unsafe是Go语言标准库中的一个包,提供了一些不安全的编程操作,本文将深入探讨Go语言中的unsafe包,介绍它的使用方法和注意事项,感兴趣的可以了解下

示例代码

package unsafe
import (
   "errors"
   "reflect"
   "unsafe"
)
type UnsafeAccessor struct {
   // fields 保存结构体中的字段信息
   // {"Name": {"offset": 1, "typ": "String"}, "Age": {"offset": 6, "typ": "Int"}}
   fields map[string]Field
   // address 结构体的起始地址
   address unsafe.Pointer
}
// NewUnsafeAccessor 初始化结构体
func NewUnsafeAccessor(entity any) *UnsafeAccessor {
   typ := reflect.TypeOf(entity).Elem()
   numField := typ.NumField()
   fields := make(map[string]Field, numField)
   for i := 0; i < numField; i++ {
      fd := typ.Field(i)
      fields[fd.Name] = Field{
         offset: fd.Offset,
         typ:    fd.Type,
      }
   }
   val := reflect.ValueOf(entity)
   return &UnsafeAccessor{
      fields:  fields,
      address: val.UnsafePointer(),
   }
}
// Field 读取字段上的数据
func (a *UnsafeAccessor) Field(field string) (any, error) {
   fd, ok := a.fields[field]
   if !ok {
      return nil, errors.New("非法字段")
   }
   // address := unsafe.Pointer(a.address + fd.offset)
   address := unsafe.Pointer(uintptr(a.address) + fd.offset)
   // 已知字段类型,如下操作
   // return *(*int8)(address), nil
   // 未知字段类型,如下操作
   return reflect.NewAt(fd.typ, address).Elem().Interface(), nil
}
// SetField 向字段上写入数据
func (a *UnsafeAccessor) SetField(field string, value any) error {
   fd, ok := a.fields[field]
   if !ok {
      return errors.New("非法字段")
   }
   // address := unsafe.Pointer(a.address + fd.offset)
   address := unsafe.Pointer(uintptr(a.address) + fd.offset)
   // 已知字段类型赋值
   // *(*int8)(address) = value.(int8)
   // 未知字段类型赋值
   reflect.NewAt(fd.typ, address).Elem().Set(reflect.ValueOf(value))
   return nil
}
type Field struct {
   // offset 字段的偏移量
   offset uintptr
   // typ 字段类型
   typ reflect.Type
}

测试代码

package unsafe
import (
   "github.com/stretchr/testify/assert"
   "testing"
)
func TestUnsafeAccessor_Field(t *testing.T) {
   testCases := []struct {
      name    string
      entity  any
      field   string
      wantRes any
      wantErr error
   }{
      {
         name: "query field value already know field type",
         entity: &struct {
            Name string
            Age  int8
         }{
            Name: "Tom",
            Age:  19,
         },
         field:   "Age",
         wantRes: int8(19),
         wantErr: nil,
      },
      {
         name: "query field value don't know field type",
         entity: &struct {
            Name string
            Age  int8
         }{
            Name: "Tom",
            Age:  19,
         },
         field:   "Age",
         wantRes: int8(19),
         wantErr: nil,
      },
   }
   for _, tc := range testCases {
      t.Run(tc.name, func(t *testing.T) {
         res, err := NewUnsafeAccessor(tc.entity).Field(tc.field)
         assert.Equal(t, tc.wantErr, err)
         if err != nil {
            return
         }
         assert.Equal(t, tc.wantRes, res)
      })
   }
}
func TestUnsafeAccessor_SetField(t *testing.T) {
   testCases := []struct {
      name    string
      entity  any
      field   string
      value   any
      wantRes any
      wantErr error
   }{
      {
         name: "set field value already know field type",
         entity: &struct {
            Name string
            Age  int8
         }{
            Name: "Tom",
         },
         field: "Age",
         value: int8(20),
         wantRes: &struct {
            Name string
            Age  int8
         }{
            Name: "Tom",
            Age:  20,
         },
         wantErr: nil,
      },
      {
         name: "set field value don't know field type",
         entity: &struct {
            Name string
            Age  int8
         }{
            Name: "Tom",
            Age:  19,
         },
         field: "Name",
         value: "Jack",
         wantRes: &struct {
            Name string
            Age  int8
         }{
            Name: "Jack",
            Age:  19,
         },
         wantErr: nil,
      },
   }
   for _, tc := range testCases {
      t.Run(tc.name, func(t *testing.T) {
         err := NewUnsafeAccessor(tc.entity).SetField(tc.field, tc.value)
         assert.Equal(t, tc.wantErr, err)
         if err != nil {
            return
         }
         assert.Equal(t, tc.wantRes, tc.entity)
      })
   }
}

错误总结

reflect: call of reflect.Value.UnsafePointer on struct Value 这是由于reflect.ValueOf(entity).UnsafePointer()引起的,因为UnsafePointer方法只能由entity是Chan、Func、Map、Pointer、Slice、UnsafePointer调用。这点大家自定去看UnsafePointer的内部实现即可。所以我们得传入一个结构体指针。

reflect.NumField of non-struct type *Struct 只是处理上一个问题所引出的另一个问题,因为我们传入了一个结构体指针,而指针结构体本身并没有什么字段信息,当我们反射指针结构体的时候,需要使用Elem方法找到指针结构体最终的结构体信息【可能有点绕】所以我们得用reflect.TypeOf().Elem()操作

疑问总结

Accessor结构体中的偏移量类型和Field的偏移量类型怎么不一样? Accessor中的记录偏移量的类型是unsafe.Pointer,Field中记录地址偏移量的类型是uintptr类型。而来在大部分情况下作用是一样的。只有一点点区别。

举例来说就像:uintptr像int类型,unsafe.Pointer像*int类型,【用string、bool距离也可以】

int和*int就是我们developer层面来说的,而uintptr和unsafe.Pointer是在GO内部层面来说的。

在垃圾回收前后,int类型的地址可能会发生变化,而*int就不是这样。*int本身保存的就是数据地址,不论有没有触发GC。或者说,GO内部层面会帮我们处理好对*int的维护。

例子讲完了,回到我们的正题,uintptr存储的是实打实的数据信息,而unsafe.Pointer存储的是数据的指针,也就是地址

什么时候用unintptr,什么时候用unsafe。Pointer呢? 抛出结论,优先使用unsafe.Pointer。其实这个也可以这样想,你什么时候用int类型,什么时候用*int类型呢?

在我们的例子中,我们是在Accessor中用的是unsafe.Pointer类型,为什么呢?因为它需要记录当前结构体的起始地址的偏移量;而在Field中用的uintptr,这是一个相对的概念。它是相对于Accessor中的偏移量来说的。

  • *(*int8)(address)是个什么鬼? 首先这肯定是一个断言操作,不用怀疑;其次,address是一个unsafe.Pointer类型。我们断言address是*(*int8)类型。具体看unsafe.Pointer的源码。清楚讲了。
  • reflect.NewAt(fd.Typ, address).Elem().Interface()是什么操作 reflect.NewAt()是将偏移量定义成fd.typ类型。

因为reflect.NewAt方法返回的是一个指针Value类型,所以需要用Elem获取其本质的结构体信息

因为这里我们需要具体的值。但是Value具体的数据是存储在其内部的ptr中,所以需要用Interface将ptr取出来,返回的是一个any类型。

reflect.ValueOf(entity).UnsafePointerreflect.ValueOf(entity).UnsafeAddr的区别

到此这篇关于初探GO中unsafe包的使用的文章就介绍到这了,更多相关GO unsafe内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Go语言实现JSON解析的方法详解

    Go语言实现JSON解析的方法详解

    在日常项目中,使用Json格式进行数据封装是比较常见的操作。本文将详细讲解如何利用Go语言实现JSON的解析,感兴趣的小伙伴可以学习一下
    2022-04-04
  • Gin框架中的路由与请求处理的实现

    Gin框架中的路由与请求处理的实现

    本文主要介绍了Gin框架中的路由与请求处理的实现,包括路径参数、查询参数和路由分组的使用,具有一定的参考价值,感兴趣的可以了解一下
    2025-06-06
  • Go语言sync.Pool对象池使用场景基本示例

    Go语言sync.Pool对象池使用场景基本示例

    这篇文章主要为大家介绍了Go语言sync.Pool对象池使用场景的基本示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • Go语言网站使用异步编程和Goroutine提高Web的性能

    Go语言网站使用异步编程和Goroutine提高Web的性能

    作为一门现代化编程语言,Go语言提供了强大的异步编程能力,使得程序员可以以更高效的方式处理并发任务,在Go语言中,使用Goroutine在单个进程中实现多任务并行处理,以及如何使用协程池来进一步提高Web服务器的处理能力,
    2024-01-01
  • Golang 获取文件md5校验的方法以及效率对比

    Golang 获取文件md5校验的方法以及效率对比

    这篇文章主要介绍了Golang 获取文件md5校验的方法以及效率对比,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-05-05
  • 解析Go 中的 rune 类型

    解析Go 中的 rune 类型

    rune类型是 Go 语言的一种特殊数字类型,Go 语言通过rune处理中文,支持国际化多语言,本文给大家介绍Go 中的 rune 类型,感兴趣的朋友一起看看吧
    2022-03-03
  • go mod 使用私有gitlab群组的解决方案

    go mod 使用私有gitlab群组的解决方案

    这篇文章主要介绍了go mod 使用私有gitlab群组的解决方案,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-05-05
  • Go单例模式与Once源码实现

    Go单例模式与Once源码实现

    这篇文章主要介绍了Go单例模式与Once源码实现,本文结合示例代码给大家讲解的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-12-12
  • 深入讲解Go语言中函数new与make的使用和区别

    深入讲解Go语言中函数new与make的使用和区别

    大家都知道Go语言中的函数new与函数make一直是新手比较容易混淆的东西,看着相似,但其实不同,不过解释两者之间的不同也非常容易,下面这篇文章主要给大家介绍了关于Go语言中函数new与make区别的相关资料,需要的朋友可以参考下。
    2017-10-10
  • Golang实践之Error创建和处理详解

    Golang实践之Error创建和处理详解

    在 C#、Java 等语言中常常使用 try...catch的方式来捕获异常,但是在Golang 对于错误处理有不同的方式,像网上也有很多对 error 处理的最佳实践的文章,其中很多其实就是对 error 的统一封装,使用规范进行约束,本文主要是记录自己对处理 Error 的一些认识和学习
    2023-09-09

最新评论