在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, e.word, 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的相互转换。
参考:
Golang反射包的实现原理(The Laws of Reflection)
有疑问加站长微信联系(非本文作者)