初探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).UnsafePointer
和reflect.ValueOf(entity).UnsafeAddr
的区别
到此这篇关于初探GO中unsafe包的使用的文章就介绍到这了,更多相关GO unsafe内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
CSP communicating sequential processes并发模型
这篇文章主要为大家介绍了CSP communicating sequential processes并发模型,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪2022-05-05
最新评论