初探GO中unsafe包的使用
示例代碼
package unsafe
import (
"errors"
"reflect"
"unsafe"
)
type UnsafeAccessor struct {
// fields 保存結(jié)構(gòu)體中的字段信息
// {"Name": {"offset": 1, "typ": "String"}, "Age": {"offset": 6, "typ": "Int"}}
fields map[string]Field
// address 結(jié)構(gòu)體的起始地址
address unsafe.Pointer
}
// NewUnsafeAccessor 初始化結(jié)構(gòu)體
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 讀取字段上的數(shù)據(jù)
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 向字段上寫入數(shù)據(jù)
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
}測(cè)試代碼
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)
})
}
}錯(cuò)誤總結(jié)
reflect: call of reflect.Value.UnsafePointer on struct Value 這是由于reflect.ValueOf(entity).UnsafePointer()引起的,因?yàn)閁nsafePointer方法只能由entity是Chan、Func、Map、Pointer、Slice、UnsafePointer調(diào)用。這點(diǎn)大家自定去看UnsafePointer的內(nèi)部實(shí)現(xiàn)即可。所以我們得傳入一個(gè)結(jié)構(gòu)體指針。
reflect.NumField of non-struct type *Struct 只是處理上一個(gè)問(wèn)題所引出的另一個(gè)問(wèn)題,因?yàn)槲覀儌魅肓艘粋€(gè)結(jié)構(gòu)體指針,而指針結(jié)構(gòu)體本身并沒(méi)有什么字段信息,當(dāng)我們反射指針結(jié)構(gòu)體的時(shí)候,需要使用Elem方法找到指針結(jié)構(gòu)體最終的結(jié)構(gòu)體信息【可能有點(diǎn)繞】所以我們得用reflect.TypeOf().Elem()操作

疑問(wèn)總結(jié)
Accessor結(jié)構(gòu)體中的偏移量類型和Field的偏移量類型怎么不一樣? Accessor中的記錄偏移量的類型是unsafe.Pointer,F(xiàn)ield中記錄地址偏移量的類型是uintptr類型。而來(lái)在大部分情況下作用是一樣的。只有一點(diǎn)點(diǎn)區(qū)別。
舉例來(lái)說(shuō)就像:uintptr像int類型,unsafe.Pointer像*int類型,【用string、bool距離也可以】
int和*int就是我們developer層面來(lái)說(shuō)的,而uintptr和unsafe.Pointer是在GO內(nèi)部層面來(lái)說(shuō)的。
在垃圾回收前后,int類型的地址可能會(huì)發(fā)生變化,而*int就不是這樣。*int本身保存的就是數(shù)據(jù)地址,不論有沒(méi)有觸發(fā)GC?;蛘哒f(shuō),GO內(nèi)部層面會(huì)幫我們處理好對(duì)*int的維護(hù)。
例子講完了,回到我們的正題,uintptr存儲(chǔ)的是實(shí)打?qū)嵉臄?shù)據(jù)信息,而unsafe.Pointer存儲(chǔ)的是數(shù)據(jù)的指針,也就是地址
什么時(shí)候用unintptr,什么時(shí)候用unsafe。Pointer呢? 拋出結(jié)論,優(yōu)先使用unsafe.Pointer。其實(shí)這個(gè)也可以這樣想,你什么時(shí)候用int類型,什么時(shí)候用*int類型呢?
在我們的例子中,我們是在Accessor中用的是unsafe.Pointer類型,為什么呢?因?yàn)樗枰涗洰?dāng)前結(jié)構(gòu)體的起始地址的偏移量;而在Field中用的uintptr,這是一個(gè)相對(duì)的概念。它是相對(duì)于Accessor中的偏移量來(lái)說(shuō)的。
*(*int8)(address)是個(gè)什么鬼? 首先這肯定是一個(gè)斷言操作,不用懷疑;其次,address是一個(gè)unsafe.Pointer類型。我們斷言address是*(*int8)類型。具體看unsafe.Pointer的源碼。清楚講了。reflect.NewAt(fd.Typ, address).Elem().Interface()是什么操作reflect.NewAt()是將偏移量定義成fd.typ類型。
因?yàn)閞eflect.NewAt方法返回的是一個(gè)指針Value類型,所以需要用Elem獲取其本質(zhì)的結(jié)構(gòu)體信息
因?yàn)檫@里我們需要具體的值。但是Value具體的數(shù)據(jù)是存儲(chǔ)在其內(nèi)部的ptr中,所以需要用Interface將ptr取出來(lái),返回的是一個(gè)any類型。
reflect.ValueOf(entity).UnsafePointer和reflect.ValueOf(entity).UnsafeAddr的區(qū)別
到此這篇關(guān)于初探GO中unsafe包的使用的文章就介紹到這了,更多相關(guān)GO unsafe內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語(yǔ)言實(shí)現(xiàn)JSON解析的方法詳解
在日常項(xiàng)目中,使用Json格式進(jìn)行數(shù)據(jù)封裝是比較常見(jiàn)的操作。本文將詳細(xì)講解如何利用Go語(yǔ)言實(shí)現(xiàn)JSON的解析,感興趣的小伙伴可以學(xué)習(xí)一下2022-04-04
Gin框架中的路由與請(qǐng)求處理的實(shí)現(xiàn)
本文主要介紹了Gin框架中的路由與請(qǐng)求處理的實(shí)現(xiàn),包括路徑參數(shù)、查詢參數(shù)和路由分組的使用,具有一定的參考價(jià)值,感興趣的可以了解一下2025-06-06
Go語(yǔ)言sync.Pool對(duì)象池使用場(chǎng)景基本示例
這篇文章主要為大家介紹了Go語(yǔ)言sync.Pool對(duì)象池使用場(chǎng)景的基本示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
Go語(yǔ)言網(wǎng)站使用異步編程和Goroutine提高Web的性能
作為一門現(xiàn)代化編程語(yǔ)言,Go語(yǔ)言提供了強(qiáng)大的異步編程能力,使得程序員可以以更高效的方式處理并發(fā)任務(wù),在Go語(yǔ)言中,使用Goroutine在單個(gè)進(jìn)程中實(shí)現(xiàn)多任務(wù)并行處理,以及如何使用協(xié)程池來(lái)進(jìn)一步提高Web服務(wù)器的處理能力,2024-01-01
Golang 獲取文件md5校驗(yàn)的方法以及效率對(duì)比
這篇文章主要介紹了Golang 獲取文件md5校驗(yàn)的方法以及效率對(duì)比,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-05-05
深入講解Go語(yǔ)言中函數(shù)new與make的使用和區(qū)別
大家都知道Go語(yǔ)言中的函數(shù)new與函數(shù)make一直是新手比較容易混淆的東西,看著相似,但其實(shí)不同,不過(guò)解釋兩者之間的不同也非常容易,下面這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言中函數(shù)new與make區(qū)別的相關(guān)資料,需要的朋友可以參考下。2017-10-10
Golang實(shí)踐之Error創(chuàng)建和處理詳解
在 C#、Java 等語(yǔ)言中常常使用 try...catch的方式來(lái)捕獲異常,但是在Golang 對(duì)于錯(cuò)誤處理有不同的方式,像網(wǎng)上也有很多對(duì) error 處理的最佳實(shí)踐的文章,其中很多其實(shí)就是對(duì) error 的統(tǒng)一封裝,使用規(guī)范進(jìn)行約束,本文主要是記錄自己對(duì)處理 Error 的一些認(rèn)識(shí)和學(xué)習(xí)2023-09-09

