初探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 向字段上寫(xiě)入數(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)文章
goalng?結(jié)構(gòu)體?方法集?接口實(shí)例詳解
這篇文章主要為大家介紹了goalng?結(jié)構(gòu)體?方法集?接口實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09go語(yǔ)言環(huán)境變量設(shè)置全過(guò)程
這篇文章主要介紹了go語(yǔ)言環(huán)境變量設(shè)置全過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05Go語(yǔ)言的數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)JSON
本文主要介紹了Go語(yǔ)言的數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)JSON,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01GoLand 中設(shè)置默認(rèn)項(xiàng)目文件夾的實(shí)現(xiàn)
本文主要介紹了GoLand 中設(shè)置默認(rèn)項(xiàng)目文件夾的實(shí)現(xiàn),默認(rèn)項(xiàng)目文件夾會(huì)在你打開(kāi)或新建項(xiàng)目時(shí)自動(dòng)預(yù)選,避免每次都需要手動(dòng)導(dǎo)航到目標(biāo)目錄,感興趣的可以了解一下2025-03-03Go語(yǔ)言Elasticsearch數(shù)據(jù)清理工具思路詳解
這篇文章主要介紹了Go語(yǔ)言Elasticsearch數(shù)據(jù)清理工具思路詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-10-10golang使用信號(hào)量熱更新的實(shí)現(xiàn)示例
這篇文章主要介紹了golang使用信號(hào)量熱更新的實(shí)現(xiàn)示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04Go語(yǔ)言實(shí)現(xiàn)的排列組合問(wèn)題實(shí)例(n個(gè)數(shù)中取m個(gè))
這篇文章主要介紹了Go語(yǔ)言實(shí)現(xiàn)的排列組合問(wèn)題,結(jié)合實(shí)例形式分析了Go語(yǔ)言實(shí)現(xiàn)排列組合數(shù)學(xué)運(yùn)算的原理與具體操作技巧,需要的朋友可以參考下2017-02-02