37

golang中反射与接口的关系

 5 years ago
source link: https://www.tuicool.com/articles/JBNnEzM
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

golang中interface底层分析 文中分析了接口的底层原理。其中接口的内部结构分两种一种是iface接口,就是有方法的接口,另一种是eface是空接口。不管是哪种都有两个字段:data、_type 代表接口变量的数据和变量类型信息。那它和反射类型有什么关系吗?今天的文章就是分析接口变量和反射变量的关系。

环境:go version go1.12.5 linux/amd64

1 类型方法 reflect.TypeOf(interface{})

示例1代码如下图:

af51a9040c72d44261fcf4ad17b374d0.png

输出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。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK