GO语言reflect反射原理浅析

GO语言reflect反射原理浅析

Go 语言反射的三大法则如下:

  • interface{} 变量可以反射出反射对象;
  • 从反射对象可以获取 interface{} 变量;
  • 要修改反射对象,其值必须可设置;

1、reflect.Type

通过下面reflect.TypeOf对于reflect.Type的构建过程可以发现,其实现原理是将传递进来的接口变量转换为底层的空接口类型emptyInterfacereflect.Type实质上是emptyInterfacetyp字段。Go语言中任何一个具体类型的底层结构都包含这一类型。

func TypeOf(i interface{}) Type {
	eface := *(*emptyInterface)(unsafe.Pointer(&i))
	return toType(eface.typ)
}
func toType(t *rtype) Type {
	if t == nil {
		return nil
	}
	return t
}

type emptyInterface struct {
	typ  *rtype
	word unsafe.Pointer
}
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
	// function for comparing objects of this type
	// (ptr to object A, ptr to object B) -> ==?
	equal     func(unsafe.Pointer, unsafe.Pointer) bool
	gcdata    *byte   // garbage collection data
	str       nameOff // string form
	ptrToThis typeOff // type for pointer to this type, may be zero
}

2、reflect.Value

reflect.Value通过reflect.ValueOf函数构建,其核心是调用了unpackEface函数。

// unpackEface converts the empty interface i to a 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}
}

type Value struct {
	typ *rtype
	ptr unsafe.Pointer
	flag
}

type flag uintptr

func (t *rtype) Kind() Kind { 
    return Kind(t.kind & kindMask) 
}

const (
    kindMask        = (1 << 5) - 1
)

unpackEface仍然是使用emptyInterface空接口构造Value类。这个类包括三个字段,其中typ保存Value类表示的类型,ptr字段表示指向数据的指针。flag字段以位图的形式存储了反射类型的元数据,其中低5位存储了类型的标志,Kind方法通过&操作快速获取emptyInterface类型的typ字段的kind字段的低5位作为存储类型的标志;6~10位表示一些特征,表示是否可以外部访问、是否可以寻址、是否是方法等。具体含义如下:

flagStickyRO    flag = 1 << 5	//结构体未导出的字段,不是嵌入的字段
flagEmbedRO     flag = 1 << 6	//结构体未导出的字段,是嵌入的字段
flagIndir       flag = 1 << 7	//间接的,val持有指向数据的指针
flagAddr        flag = 1 << 8	//v.CanAddr为真,可寻址的
flagMethod      flag = 1 << 9	//是一个方法
flagRO          flag = flagStickyRO | flagEmbedRO	//结构体未导出字段

flag字段的其余位存储了方法的index序号,代表第几个方,只有在当前的value是方法类型时才会用到。flagIndir字段代表间接的,因为反射或接口的值都是指针。另外容器类型如切片、map、channel也被认为是间接的,因为需要当前容器的指针间接找到存储在其内部的元素。

3、Interface方法

reflect.ValueOf函数通过空接口构建reflect.Value类型,同时Value类也可以调用Interface方法将反射类型转换为空接口。

func (v Value) Interface() (i interface{}) {
	return valueInterface(v, true)
}

该方法的核心是调用packEface方法。e.typ = t是将reflect.Value中的类型赋值给空接口中的类型,并根据vflag字段确定空接口的word字段。

当反射中存储的是容器类型时,这时存储的是数据的地址,我们当然不希望返回的值对原始数据造成任何干扰。当出现这种情况是,ifaceIndir(t)函数返回值为true,这时会生成一个新的值赋值给word字段。

type emptyInterface struct {
	typ  *rtype
	word unsafe.Pointer
}
// packEface converts v to the empty interface.
func packEface(v Value) interface{} {
	t := v.typ
	var i interface{}
	e := (*emptyInterface)(unsafe.Pointer(&i))
	// First, fill in the data portion of the interface.
	switch {
	case ifaceIndir(t):
		if v.flag&flagIndir == 0 {
			panic("bad indir")
		}
		// Value is indirect, and so is the interface we're making.
		ptr := v.ptr
		if v.flag&flagAddr != 0 {
			// TODO: pass safe boolean from valueInterface so
			// we don't need to copy if safe==true?
			c := unsafe_New(t)
			typedmemmove(t, c, ptr)
			ptr = c
		}
		e.word = ptr
	case v.flag&flagIndir != 0:
		// Value is indirect, but interface is direct. We need
		// to load the data at v.ptr into the interface data word.
		e.word = *(*unsafe.Pointer)(v.ptr)
	default:
		// Value is direct, and so is the interface.
		e.word = v.ptr
	}
	// Now, fill in the type portion. We're very careful here not
	// to have any operation between the e.word and e.typ assignments
	// that would let the garbage collector observe the partially-built
	// interface value.
	e.typ = t
	return i
}
// ifaceIndir 返回t是否间接存储在接口中。
func ifaceIndir(t *rtype) bool {
	return t.kind&kindDirectIface == 0
}

4、Elem方法

当我们修改反射中的数据时,对于初学者可能会报如下的错误:

func main() {
	defer func() {
		err := recover()
		fmt.Println("------", err)
	}()
	a := 3
	r := reflect.ValueOf(&a)
	r.Set(reflect.ValueOf(10))
}
//output:------ reflect: reflect.Value.Set using unaddressable value

这时在reflect.Value中存储的已经是变量的指针了,仍然会报错。这时因为在reflect.ValueOf函数构建reflect.Value时只判断了变量是否为间接地址,所以会报错。

func (v Value) Set(x Value) {
	v.mustBeAssignable()
	x.mustBeExported() // do not let unexported x leak
	var target unsafe.Pointer
	if v.kind() == Interface {
		target = v.ptr
	}
	x = x.assignTo("reflect.Set", v.typ, target)
	if x.flag&flagIndir != 0 {
		if x.ptr == unsafe.Pointer(&zeroVal[0]) {
			typedmemclr(v.typ, v.ptr)
		} else {
			typedmemmove(v.typ, v.ptr, x.ptr)
		}
	} else {
		*(*unsafe.Pointer)(v.ptr) = x.ptr
	}
}

其实Elem方法的功能是返回接口内部包含的或指针指向的数据值,如果flag标志了reflect.Value是间接的,则会返回数据的真实地址,如果是直接的指针,则会返回本身,并修改flagAddr为可赋值的。

func (v Value) Elem() Value {
	k := v.kind()
	switch k {
	case Interface:
        //获取接口中的内容
		var eface interface{}
		if v.typ.NumMethod() == 0 {
			eface = *(*interface{})(v.ptr)
		} else {
			eface = (interface{})(*(*interface {
				M()
			})(v.ptr))
		}
		x := unpackEface(eface)
		if x.flag != 0 {
			x.flag |= v.flag.ro()
		}
		return x
	case Ptr:
        //获取指针指向的内容
		ptr := v.ptr
		if v.flag&flagIndir != 0 {
			ptr = *(*unsafe.Pointer)(ptr)
		}
		// The returned value's address is v's value.
		if ptr == nil {
			return Value{}
		}
		tt := (*ptrType)(unsafe.Pointer(v.typ))
		typ := tt.elem
		fl := v.flag&flagRO | flagIndir | flagAddr
		fl |= flag(typ.Kind())
		return Value{typ, ptr, fl}
	}
	panic(&ValueError{"reflect.Value.Elem", v.kind()})
}

参考文献:《GO语言底层原理剖析》 郑建勋著

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值