golang中反射與介面的關係
在golang中interface底層分析文中分析了介面的底層原理。其中介面的內部結構分兩種一種是iface介面,就是有方法的介面,另一種是eface是空介面。不管是哪種都有兩個欄位:data、_type 代表介面變數的資料和變數型別資訊。那它和反射型別有什麼關係嗎?今天的文章就是分析介面變數和反射變數的關係。
環境:go version go1.12.5 linux/amd64
1 型別方法 reflect.TypeOf(interface{})
示例1程式碼如下圖:
輸出I
變數x的型別是I,那將x傳入TypeOf()函式之後 Name()函式是如何獲取到變數x的型別資訊的呢? 接下來我們一步一步分析,第12行程式碼的Name()函式是如何獲取到型別I的。
看一下TypeOf(interface)函式的實現:
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
複製程式碼
我們發現TypeOf的引數是介面型別,就是說變數x的副本被包裝成了runtime/runtime2.go中定義的eface(空介面)。然後將eface強制轉換成了emptyInterface,如下是reflect和runtime包下定義兩個空介面:
//reflect/type.go
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
//runtime/runtime2.go
type eface struct {
_type *_type
data unsafe.Pointer
}
複製程式碼
發現和runtime包中的空介面很像,emptyInterface.word,runtime.eface欄位型別是相同的。那就看看rtype和_type是否相同呢?
//reflect/type.go
type rtype struct {
size uintptr
ptrdata uintptr // number of bytes in the type that can contain pointers
hash uint32 // hash of type; avoids computation in hash tables
tflag tflag // extra type information flags
align uint8 // alignment of variable with this type
fieldAlign uint8 // alignment of struct field with this type
kind uint8 // enumeration for C
alg *typeAlg // algorithm table
gcdata *byte // garbage collection data
str nameOff // string form
ptrToThis typeOff // type for pointer to this type,may be zero
}
//runtime/type.go
type _type struct {
size uintptr
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32
tflag tflag
align uint8
fieldalign uint8
kind uint8
alg *typeAlg
// gcdata stores the GC type data for the garbage collector.
// If the KindGCProg bit is set in kind,gcdata is a GC program.
// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
gcdata *byte
str nameOff
ptrToThis typeOff
}
複製程式碼
完全一樣所以就可以毫無顧慮轉換了。 也就是說emptyInterface.rtype結構體裡已經有x的型別資訊了。接下來繼續看Name()函式是如何獲取到型別的字串資訊的: Type(interface{})函式裡有個toType()函式,去看一下:
//reflect/type.go
func toType(t *rtype) Type {
if t == nil {
return nil
}
return t
}
複製程式碼
上面程式碼是將*rtype直接轉換成了Type型別了,那Type型別是啥?
//reflect/type.go
type Type interface {
......
Name() string
......
}
複製程式碼
其實Type是個介面型別。
那*rtype肯定實現了此介面中的方法,其中就包括Name()方法。找到了Name()的實現函式如下。如果不先看Name()的實現,其實也能猜到:就是從*rtype
型別中定位資料獲取資料並返回給呼叫者的過程,因為*rtype
裡面有包含值變數型別等資訊。
func (t *rtype) Name() string {
if t.tflag&tflagNamed == 0 {
return ""
}
s := t.String()
i := len(s) - 1
for i >= 0 {
if s[i] == '.' {
break
}
i--
}
return s[i+1:]
}
複製程式碼
重點看一下t.String()
func (t *rtype) String() string {
s := t.nameOff(t.str).name()
if t.tflag&tflagExtraStar != 0 {
return s[1:]
}
return s
}
複製程式碼
再重點看一下nameOff():
func (t *rtype) nameOff(off nameOff) name {
return name{(*byte)(resolveNameOff(unsafe.Pointer(t),int32(off)))}
}
複製程式碼
從名字可以猜測出Off是Offset的縮寫(這個函式裡面的具體邏輯就探究了)進行偏移從而得到對應記憶體地址的值。 String()函式中的name()函式如下:
func (n name) name() (s string) {
if n.bytes == nil {
return
}
b := (*[4]byte)(unsafe.Pointer(n.bytes))
hdr := (*stringHeader)(unsafe.Pointer(&s))
hdr.Data = unsafe.Pointer(&b[3])
hdr.Len = int(b[1])<<8 | int(b[2])
return s
}
複製程式碼
name()函式的邏輯是根據nameOff()返回的*byte(就是型別資訊的首地址)計算出字串的Data和Len位置,然後通過返回值&s包裝出stringHeader(字串原型)並將Data,Len賦值給字串原型,從而將返回值s賦值。
總結 : 普通的變數 => 反射中Type型別 => 獲取變數型別資訊 。
1,變數副本包裝成空介面runtime.eface
。
2,將runtime.eface
轉換成reflat.emptyInterface
(結構都一樣)。
3,將*emptyInterface.rtype
轉換成 reflect.Type
介面型別(包裝成runtime.iface結構體型別)。
4,介面型別變數根據runtime.iface.tab.fun
找到reflat.Name()函式。
5,reflect.Name()根據*rtype
結構體str(nameoff型別)找到偏移量。
6,根據偏移量和基地址(基地址沒有在*rtype
中,這塊先略過)。找到型別記憶體塊。
7,包裝成stringHeader型別返回給呼叫者。
其實核心就是將runtime包中的eface結構體資料複製到reflect包中的emptyInterface中然後在從裡面獲取相應的值型別資訊。
refact.Type介面裡面的其他方法就不在在這裡說了,核心思想就是圍繞reflat.emptyInterface中的資料進行查詢等操作。
2 值方法 reflect.ValueOf(interface{})
package main
import (
"reflect"
"fmt"
)
func main() {
var a = 3
v := reflect.ValueOf(a)
i := v.Interface()
z := i.(int)
fmt.Println(z)
}
複製程式碼
看一下reflect.ValueOf()實現:
func ValueOf(i interface{}) Value {
....
return unpackEface(i)
}
複製程式碼
返回值是Value型別:
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag //先忽略
}
複製程式碼
Value是個結構體型別,包含著值變數的型別和資料指標。
func unpackEface(i interface{}) Value {
e := (*emptyInterface)(unsafe.Pointer(&i))
t := e.typ
if t == nil {
return Value{}
}
f := flag(t.Kind())
if ifaceIndir(t) {
f |= flagIndir
}
return Value{t,e.word,f}
}
複製程式碼
具體實現是在unpackEface(interface{})中:
e := (*emptyInterface)(unsafe.Pointer(&i))
複製程式碼
和上面一樣從*runtime.eface
轉換成*reflect.emptyInterface了
。
最後包裝成Value:
return Value{t,f}
複製程式碼
繼續看一下示例程式碼:
i := v.Interface()
複製程式碼
的實現:
func (v Value) Interface() (i interface{}) {
return valueInterface(v,true)
}
func valueInterface(v Value,safe bool) interface{} {
......
return packEface(v)
}
func packEface(v Value) interface{} {
t := v.typ
var i interface{}
e := (*emptyInterface)(unsafe.Pointer(&i))
switch {
case ifaceIndir(t):
if v.flag&flagIndir == 0 {
panic("bad indir")
}
//將值的資料資訊指標賦值給ptr
ptr := v.ptr
if v.flag&flagAddr != 0 {
c := unsafe_New(t)
typedmemmove(t,c,ptr)
ptr = c
}
//為空介面賦值
e.word = ptr
case v.flag&flagIndir != 0:
e.word = *(*unsafe.Pointer)(v.ptr)
default:
e.word = v.ptr
}
//為空介面賦值
e.typ = t
return i
}
複製程式碼
最終呼叫了packEface()函式,從函式名字面意思理解是打包成空介面。 邏輯是:從value.typ資訊包裝出reflect.emptyInterface結構體資訊,然後將reflect.eface寫入i變數中,又因為i是interface{}型別,編譯器又會將i轉換成runtime.eface型別。
z := i.(int)
複製程式碼
根據字面量int編譯器會從runtime.eface._type中查詢int的值是否匹配,如果不匹配panic,匹配i的值賦值給z。
總結:從值變數 => value反射變數 => 介面變數:
1,包裝成value
型別。
2,從value
型別中獲取rtype包裝成reflect.emptyInterface
型別。
3,reflect.eface
編譯器轉換成runtime.eface
型別。
4,根據程式z :=i(int) 從runtime.eface._type
中查詢是否匹配。
5,匹配將值賦值給變數z。
總結:Value反射型別轉interface{}型別核心還是reflet.emptyInterface與runtime.eface的相互轉換。
參考: