GO语言reflect反射原理浅析
Go 语言反射的三大法则如下:
- 从
interface{}
变量可以反射出反射对象; - 从反射对象可以获取
interface{}
变量; - 要修改反射对象,其值必须可设置;
1、reflect.Type
通过下面reflect.TypeOf
对于reflect.Type
的构建过程可以发现,其实现原理是将传递进来的接口变量转换为底层的空接口类型emptyInterface
。reflect.Type
实质上是emptyInterface
的typ
字段。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
中的类型赋值给空接口中的类型,并根据v
的flag
字段确定空接口的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语言底层原理剖析》 郑建勋著