阅读 314

go 反射

前提

介绍 go 中的反射之前,我们需要先介绍一下 interface。go 中的 interface 分类两种,efaceiface

eface

go 中所有的类型的数据都可以转成 eface ,对应 reflect 中的 emptyInterface

type eface struct {
	_type *_type // 类型信息
	data  unsafe.Pointer // 指向的值
}

type _type struct {
	size       uintptr  // 类型占用内容大小
	ptrdata    uintptr  // 包含所有指针的内存前缀的大小
	hash       uint32   // 类型 hash
	tflag      tflag    // 标记位,主要用于反射
	align      uint8    // 对齐字段
	fieldAlign uint8   // 当前结构字段的对齐字节数
	kind       uint8   // 基础类型枚举值
	equal func(unsafe.Pointer, unsafe.Pointer) bool // 比较两个形参对应对象的类型是否相等
	gcdata    *byte // GC 类型的数据
	str       nameOff // 类型名称字符串在二进制文件段中的偏移量
	ptrToThis typeOff // 类型元信息指针在二进制文件段中的偏移量
}
复制代码

rutime.runtime2.go 文件

eface 主要包括类型信息和值的指针,非常好理解。其中 data 指针指向的内存包含了类型和值的信息,也就是说 data 指针指向了 eface 本身

iface

iface 主要用来表示实现了 interface 的数据,对应 reflect 中的 nonEmptyInterface

type iface struct {
	tab  *itab
	data unsafe.Pointer
}

type itab struct {
	inter *interfacetype // 接口静态类型, 接口的抽象表示,也就是静态的接口,不是实际的 struct
	_type *_type // 实际类型
	hash  uint32 // copy of _type.hash. Used for type switches. 和 _type 中的 hash 一样,用来类型断言
	_     [4]byte
	fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter. 接口实现的函数,跟接口类型保持一致
}
type interfacetype struct {
	typ     _type // interface 的类型
	pkgpath name // 包信息
	mhdr    []imethod // interface 的 method 抽象,不是实际的被实现的 method
}
复制代码

runtime.runtime2.go 文件

iface 类型字段中包含类型的动态和静态信息,以及包和一些函数信息。

eface 和 iface 的转换

我们知道 任何指针都可以转换成 unsafe.Pointer, unsafe.Pointer 也可以转换成任意的指针

在使用 unsafe.Pointer 进行转换时,不会进行类型检查,只是粗暴的把类型替换,指针的地址和实际的值都不会变化。

假如使用 unsafe.Pointer 进行转换的两种数据类型差异很大,转换之后使用的时候也会出错。这也是叫 unsafe 的原因。

对于 efaceiface假如 eface 的类型是 Interface,那么 eface 中的 _type 字段和 iface 中的 inter 是可以通过 unsafe.Pointer 进行转换的

首先,两者都是指针,所以可以转成 unsafe.Pointer。其次如果我们仔细观察,会发现 inter 的第一个字段类型是 _type

所以可以进行转换,并且转换之后 interfacetype 的其他字段也是有值的。说明 efacetype 类型并不是只有显示定义的字段,还存在一些隐藏的字段,根据类型的不同,拥有不同的字段。

reflect.Type

首先现看一下 reflect.TypeOf 代码,Type 是 interface*rtype 实现了 Type

func TypeOf(i interface{}) Type {
	// 转成 eface
	eface := *(*emptyInterface)(unsafe.Pointer(&i))
	return toType(eface.typ)
}

type emptyInterface struct {
	typ  *rtype
	word unsafe.Pointer
}
复制代码

上面我们介绍 eface 的时候说了, runtime.efacereflect.emptyInterface 是一样的,所以这里直接通过 unsafe.Pointer 转换成 emptyInterface ,然后取其中的 typ 属性。 reflect.Type 相关的操作都是基于 *rtype

go 中的基本类型总共 26 种,在反射中也有枚举体现

type Kind uint

const (
	Invalid Kind = iota
	Bool
	Int
	Int8
	Int16
	Int32
	Int64
	Uint
	Uint8
	Uint16
	Uint32
	Uint64
	Uintptr
	Float32
	Float64
	Complex64
	Complex128
	Array
	Chan
	Func
	Interface
	Map
	Ptr
	Slice
	String
	Struct
	UnsafePointer
)
复制代码

在操作 reflect.Type 时很多类型都有对应的专属 type,不过只有 Interface 类型的数据会有一些特殊逻辑。

下面是各种不同类型对应的不同结构体

type arrayType struct {
	rtype
	elem  *rtype // array element type
	slice *rtype // slice type
	len   uintptr
}

type chanType struct {
	rtype
	elem *rtype  // channel element type
	dir  uintptr // channel direction (ChanDir) chan 的方向
}

type funcType struct {
	rtype
	inCount  uint16  // 输入参数
	outCount uint16 // top bit is set if last input parameter is ... 输出参数
}

type structType struct {
	rtype
	pkgPath name
	fields  []structField // sorted by offset
}

type ptrType struct {
	rtype // 指针的类型
	elem *rtype // pointer element (pointed at) type  指针指向的元素的类型(静态类型)
}

type sliceType struct {
	rtype
	elem *rtype // slice element type
}

type mapType struct {
	rtype
	key    *rtype // map key type
	elem   *rtype // map element (value) type
	bucket *rtype // internal bucket structure
	// function for hashing keys (ptr to key, seed) -> hash
	hasher     func(unsafe.Pointer, uintptr) uintptr
	keysize    uint8  // size of key slot
	valuesize  uint8  // size of value slot
	bucketsize uint16 // size of bucket
	flags      uint32
}

type interfaceType struct {
	rtype
	pkgPath name      // import path
	methods []imethod // sorted by hash
}
复制代码

以上所有的类型都可以通过 *rtype 转换

下面我们通过分析 reflect.Type 的几个函数,窥一斑而见全豹,来聊一聊反射是怎么取 _type 中的数据,进一步了解各个类型的不同之处。

Method

获取函数需要区分是否为 Interface 类型,假如是 Interface 类型,把 *rtype 转成 *interfaceType 。因为 interfaceType 类型中包含了函数信息字段,所以直接根据索引取一下就行了,这块逻辑是 easy case。

func (t *interfaceType) Method(i int) (m Method) {
	if i < 0 || i >= len(t.methods) {
		return
	}
	p := &t.methods[i]
	// 构造 method
	pname := t.nameOff(p.name)
	m.Name = pname.name()
	if !pname.isExported() {
		m.PkgPath = pname.pkgPath()
		if m.PkgPath == "" {
			m.PkgPath = t.pkgPath.name()
		}
	}
	m.Type = toType(t.typeOff(p.typ))
	m.Index = i
	return
}

type imethod struct {
	name nameOff // name of method
	typ  typeOff // .(*FuncType) underneath  指向 method 的 type 类型
}
复制代码

interface 类型取 method 的代码

假如是其他类型,就需要导出函数信息了。因为函数信息并没有显示的以字段的形式,所以需要组成临时的结构体取一下

func (t *rtype) exportedMethods() []method {
	// 获取存储函数列表的 uncommon
	ut := t.uncommon()
	if ut == nil {
		return nil
	}
	// 获取导出的函数列表
	return ut.exportedMethods()
}


func (t *rtype) uncommon() *uncommonType {
	if t.tflag&tflagUncommon == 0 {
		return nil
	}
	switch t.Kind() {
	case Struct:
		return &(*structTypeUncommon)(unsafe.Pointer(t)).u
	case Ptr:
		type u struct {
			ptrType
			u uncommonType
		}
		return &(*u)(unsafe.Pointer(t)).u
	case Func:
		type u struct {
			funcType
			u uncommonType
		}
		return &(*u)(unsafe.Pointer(t)).u
	case Slice:
		type u struct {
			sliceType
			u uncommonType
		}
		return &(*u)(unsafe.Pointer(t)).u
	case Array:
		type u struct {
			arrayType
			u uncommonType
		}
		return &(*u)(unsafe.Pointer(t)).u
	case Chan:
		type u struct {
			chanType
			u uncommonType
		}
		return &(*u)(unsafe.Pointer(t)).u
	case Map:
		type u struct {
			mapType
			u uncommonType
		}
		return &(*u)(unsafe.Pointer(t)).u
	case Interface:
		type u struct {
			interfaceType
			u uncommonType
		}
		return &(*u)(unsafe.Pointer(t)).u
	default:
		type u struct {
			rtype
			u uncommonType
		}
		return &(*u)(unsafe.Pointer(t)).u
	}
}

func (t *uncommonType) exportedMethods() []method {
	if t.xcount == 0 {
		return nil
	}
	return (*[1 << 16]method)(add(unsafe.Pointer(t), uintptr(t.moff), "t.xcount > 0"))[:t.xcount:t.xcount]
}
复制代码

uncommon() 函数

上面的 uncommonType 是存储函数信息的结构体

// 储存函数的类型
type uncommonType struct {
	pkgPath nameOff // import path; empty for built-in types like int, string
	mcount  uint16  // number of methods
	xcount  uint16  // number of exported methods 可导出的函数数量
	moff    uint32  // offset from this uncommontype to [mcount]method 函数数组的偏移量
	_       uint32  // unused
}

type method struct {
	name nameOff // name of method
	mtyp typeOff // method type (without receiver) 函数类型
	ifn  textOff // fn used in interface call (one-word receiver)
	tfn  textOff // fn used for normal method call
}
复制代码

uncommonType 类型

有了 uncommonType 就可以获取函数的类型信息了

func (t *rtype) Method(i int) (m Method) {
	// 假如是 interface
	if t.Kind() == Interface {
		tt := (*interfaceType)(unsafe.Pointer(t))
		return tt.Method(i)
	}
	// 获取导出的函数列表
	methods := t.exportedMethods()
	if i < 0 || i >= len(methods) {
		panic("reflect: Method index out of range")
	}
	p := methods[i]
	// 取函数的名字
	pname := t.nameOff(p.name)
	m.Name = pname.name()
	fl := flag(Func)
	mtyp := t.typeOff(p.mtyp)
	ft := (*funcType)(unsafe.Pointer(mtyp))
	in := make([]Type, 0, 1+len(ft.in()))
	// 先把当前类型加到 in 里
	in = append(in, t)
	// 再把参数类型加到 in 里
	for _, arg := range ft.in() {
		in = append(in, arg)
	}
	// 返回值类型数组
	out := make([]Type, 0, len(ft.out()))
	for _, ret := range ft.out() {
		out = append(out, ret)
	}

	// 构建函数
	mt := FuncOf(in, out, ft.IsVariadic())
	m.Type = mt
	tfn := t.textOff(p.tfn)
	fn := unsafe.Pointer(&tfn)
	m.Func = Value{mt.(*rtype), fn, fl}

	m.Index = i
	return m
}
复制代码

*rtype 的 Method 方法

从上面的代码可以看出来,在取到 method 之后,会根据 method 转成 Method,而 Method 会把接收者当做第一个入参,所以需要 FuncOf 重新组建一个 *rtype, 当做 MethodType。所以假如想使用 Method.Func 调用 Call 方法时,需要把接收者当做第一个参数传入。

FuncOf 方法比较长,这里就不贴代码了。简单说明一下主要逻辑

  • 新建一个 funcType
  • 赋值入参和出参,以及 hash 值(夹杂着可变函数的判断)
  • 从缓存中找相同类型的 funcType,有就返回
  • 从链接器等中查找,不管有没有都会放进缓存然后返回

Field

获取 Field 的类型必须是 Struct

func (t *rtype) Field(i int) StructField {
	if t.Kind() != Struct {
		panic("reflect: Field of non-struct type " + t.String())
	}
	// 转成 structType
	tt := (*structType)(unsafe.Pointer(t))
	return tt.Field(i)
}
复制代码

*rtype 的 Field 方法

可以看到先判断类型,然后转成 *structType

type structType struct {
	rtype
	pkgPath name
	fields  []structField // sorted by offset
}
复制代码

structType 结构体

structType获取 Field 的方法非常简单

func (t *structType) Field(i int) (f StructField) {
	if i < 0 || i >= len(t.fields) {
		panic("reflect: Field index out of bounds")
	}
	p := &t.fields[i]
	f.Type = toType(p.typ)
	f.Name = p.name.name()
	f.Anonymous = p.embedded()
	if !p.name.isExported() {
		f.PkgPath =  t.pkgPath.name()
	}
	if tag := p.name.tag(); tag != "" {
		f.Tag = StructTag(tag)
	}
	f.Offset = p.offset()
	f.Index = []int{i}
	return
}
复制代码

Implements

func (t *rtype) Implements(u Type) bool {
	if u == nil {
		panic("reflect: nil type passed to Type.Implements")
	}
	if u.Kind() != Interface {
		panic("reflect: non-interface type passed to Type.Implements")
	}
	return implements(u.(*rtype), t)
}
复制代码

*rtype 的 Implements 方法

这个方法含义是:t 是否实现了 u

上面要求 u 的类型必须是 Interface,在 go 中,我们一般这么获取 u 这种类型: reflect.TypeOf((*fmt.Stringer)(nil)).Elem()

声明一个空的 interface 指针,然后取它的 Elem。为什么这种方法可以取到呢,就需要看一下 Elem 方法了

func (t *rtype) Elem() Type {
	switch t.Kind() {
	case Array:
		tt := (*arrayType)(unsafe.Pointer(t))
		return toType(tt.elem)
	case Chan:
		tt := (*chanType)(unsafe.Pointer(t))
		return toType(tt.elem)
	case Map:
		tt := (*mapType)(unsafe.Pointer(t))
		return toType(tt.elem)
	case Ptr:
		tt := (*ptrType)(unsafe.Pointer(t))
		return toType(tt.elem)
	case Slice:
		tt := (*sliceType)(unsafe.Pointer(t))
		return toType(tt.elem)
	}
	panic("reflect: Elem of invalid type " + t.String())
}
复制代码

*rtype 的 Elem 方法

按照上面的代码,我们知道 t 现在的 Kind 是指针,所以转成了 ptrType,然后取 ptrType.elem 字段作为类型。所以 elem字段指向的应该是静态类型。以上 ArrayChan 等等的 elem 都是静态类型。

接着 implements 的逻辑。implements 的方法主要有两个分支判断,参数顺序有点变化,下面代码里的 V 其实 t,下面代码里的 t 其实是 u

  • 假如 t 是 Interface 类型

    if V.Kind() == Interface {
    		v := (*interfaceType)(unsafe.Pointer(V))
    		i := 0
    		for j := 0; j < len(v.methods); j++ {
    			tm := &t.methods[i]
    			tmName := t.nameOff(tm.name)
    			vm := &v.methods[j]
    			vmName := V.nameOff(vm.name)
    			// 函数名称和类型一样就行
    			// 得保证相同的入参和出参的函数用的一个 rtype
    			if vmName.name() == tmName.name() && V.typeOff(vm.typ) == t.typeOff(tm.typ) {
    				if !tmName.isExported() {
    					tmPkgPath := tmName.pkgPath()
    					if tmPkgPath == "" {
    						tmPkgPath = t.pkgPath.name()
    					}
    					vmPkgPath := vmName.pkgPath()
    					if vmPkgPath == "" {
    						vmPkgPath = v.pkgPath.name()
    					}
    					if tmPkgPath != vmPkgPath {
    						continue
    					}
    				}
    				if i++; i >= len(t.methods) {
    					return true
    				}
    			}
    		}
    		return false
    	}
    复制代码
  • 假如不是 Interface 类型

    // 不是 interface,判断实际的函数
    	v := V.uncommon()
    	if v == nil {
    		return false
    	}
    	i := 0
    	vmethods := v.methods()
    	for j := 0; j < int(v.mcount); j++ {
    		tm := &t.methods[i]
    		tmName := t.nameOff(tm.name)
    		vm := vmethods[j]
    		vmName := V.nameOff(vm.name)
    		if vmName.name() == tmName.name() && V.typeOff(vm.mtyp) == t.typeOff(tm.typ) {
    			if !tmName.isExported() {
    				tmPkgPath := tmName.pkgPath()
    				if tmPkgPath == "" {
    					tmPkgPath = t.pkgPath.name()
    				}
    				vmPkgPath := vmName.pkgPath()
    				if vmPkgPath == "" {
    					vmPkgPath = V.nameOff(v.pkgPath).name()
    				}
    				if tmPkgPath != vmPkgPath {
    					continue
    				}
    			}
    			if i++; i >= len(t.methods) {
    				return true
    			}
    		}
    	}
    复制代码

我们可以看到上面两块代码的逻辑一致且简单,都是判断类型和名称是否一致,假如是非导出的函数需要再判断包名是否一致

AssignableTo

func (t *rtype) AssignableTo(u Type) bool {
	if u == nil {
		panic("reflect: nil type passed to Type.AssignableTo")
	}
	uu := u.(*rtype)
	// 判断类型
	return directlyAssignable(uu, t) || implements(uu, t)
}
复制代码

*rtype 的 AssignableTo 方法

含义:t 是否可以赋值给 u

是否实现的逻辑我们已经看过了,直接看 directlyAssignable 的逻辑

func directlyAssignable(T, V *rtype) bool {
	// x's type V is identical to T?
	if T == V {
		return true
	}

	// Otherwise at least one of T and V must not be defined
	// and they must have the same kind.
	if T.hasName() && V.hasName() || T.Kind() != V.Kind() {
		return false
	}

	if T.Kind() == Chan && specialChannelAssignability(T, V) {
		return true
	}

	// x's type T and V must have identical underlying types.
	return haveIdenticalUnderlyingType(T, V, true)
}
复制代码

reflect.type.go 中的 directlyAssignable 方法

解释一下上面的逻辑:

  • *rtype相等,可赋值
  • Kind 必须相等,并且其中一个类型的 name 必须为空
  • 类型为 chan 时,需要判断 Elemchan 的方向

关于类型的名称,有一些基本类型是有名称,一些没有

  • bool - Complex128 为 1-16 有name
  • array 17 无 name
  • chan 50-18 无 name
  • func 为 51-19 无 name
  • interface 20 有 name
  • map 53-21 无 name
  • ptr 54-22 无 name
  • slice 23 无 name
  • string 24 有 name
  • struct 25 匿名的无 name,其他的有
  • unsafe.Pointer 58-26 有

type 关键字声明的类型都是有 name 的,所以 struct 类型的匿名数据才会没有 name

接着看 haveIdenticalUnderlyingType 方法

if Bool <= kind && kind <= Complex128 || kind == String || kind == UnsafePointer {
		return true
	}

	// Composite types.
	switch kind {
	case Array:
		return T.Len() == V.Len() && haveIdenticalType(T.Elem(), V.Elem(), cmpTags)

	case Chan:
		return V.ChanDir() == T.ChanDir() && haveIdenticalType(T.Elem(), V.Elem(), cmpTags)

	case Func:
		t := (*funcType)(unsafe.Pointer(T))
		v := (*funcType)(unsafe.Pointer(V))
		if t.outCount != v.outCount || t.inCount != v.inCount {
			return false
		}
		for i := 0; i < t.NumIn(); i++ {
			if !haveIdenticalType(t.In(i), v.In(i), cmpTags) {
				return false
			}
		}
		for i := 0; i < t.NumOut(); i++ {
			if !haveIdenticalType(t.Out(i), v.Out(i), cmpTags) {
				return false
			}
		}
		return true

	case Interface:
		t := (*interfaceType)(unsafe.Pointer(T))
		v := (*interfaceType)(unsafe.Pointer(V))
		if len(t.methods) == 0 && len(v.methods) == 0 {
			return true
		}
		return false

	case Map:
		return haveIdenticalType(T.Key(), V.Key(), cmpTags) && haveIdenticalType(T.Elem(), V.Elem(), cmpTags)

	case Ptr, Slice:
		return haveIdenticalType(T.Elem(), V.Elem(), cmpTags)

	case Struct:
		t := (*structType)(unsafe.Pointer(T))
		v := (*structType)(unsafe.Pointer(V))
		if len(t.fields) != len(v.fields) {
			return false
		}
		if t.pkgPath.name() != v.pkgPath.name() {
			return false
		}
		for i := range t.fields {
			tf := &t.fields[i]
			vf := &v.fields[i]
			if tf.name.name() != vf.name.name() {
				return false
			}
			if !haveIdenticalType(tf.typ, vf.typ, cmpTags) {
				return false
			}
			if cmpTags && tf.name.tag() != vf.name.tag() {
				return false
			}
			if tf.offsetEmbed != vf.offsetEmbed {
				return false
			}
		}
		return true
	}
复制代码

其中简单类型只要 kind 一致就可以,复杂类型各有各的判断,不过逻辑都很简单

reflect.Value

reflect.Value 主要通过 reflect.ValueOf 获取。reflect.ValueOf 主要是调用 unpackEface方法开箱成 Value

func unpackEface(i interface{}) Value {
	// 转成 eface
	e := (*emptyInterface)(unsafe.Pointer(&i))
	// NOTE: don't read e.word until we know whether it is really a pointer or not.
	t := e.typ
	if t == nil {
		return Value{}
	}
	f := flag(t.Kind())
	if ifaceIndir(t) {
		f |= flagIndir
	}
	return Value{t, e.word, f}
}

func ifaceIndir(t *rtype) bool {
	return t.kind&kindDirectIface == 0
}

kindDirectIface = 1 << 5 // 32
复制代码

unpackEface 函数

reflect.TypeOf 很像,都是先转成 emptyInterface。这里有一个逻辑 if ifaceIndir(t) { f |= flagIndir},假如 t 的原始 kind 位于 32 等于 0,那么就是间接的。大部分类型都是间接的,只有以下几种类型为直接的:

  • map
  • chan
  • func
  • ptr
  • unsafe.Pointer

对于直接或者间接的类型,在装箱成 Interface 的时候(也就是调用 Interface() 方法时)会有不同的处理逻辑。

同分析 reflect.Type 一样,我们分析 reflect.Value 也通过几个关键方法,来看一看 reflect.Value 的实现

Method

获取 method 的 Value

func (v Value) Method(i int) Value {
	if v.typ == nil {
		panic(&ValueError{"reflect.Value.Method", Invalid})
	}
	// 本身就是个 method
	if v.flag&flagMethod != 0 || uint(i) >= uint(v.typ.NumMethod()) {
		panic("reflect: Method index out of range")
	}
	if v.typ.Kind() == Interface && v.IsNil() {
		panic("reflect: Method on nil interface value")
	}
	// 加上函数相关的 flag 补码
	fl := v.flag & (flagStickyRO | flagIndir) // Clear flagEmbedRO
	fl |= flag(Func)
	fl |= flag(i)<<flagMethodShift | flagMethod
	// 这里的 type 并不是 Method 的 type, 而是方法接收者的 type
	return Value{v.typ, v.ptr, fl}
}
复制代码

Value 的 Method 方法

Method 方法的代码很简单,就是把 flag 加上一些标志位。typeptr 都没有变化,这么做的意义会 Call 方法中了解到。

Call

func (v Value) Call(in []Value) []Value {
	v.mustBe(Func)
	v.mustBeExported()
	return v.call("Call", in)
}
复制代码

Value 的 Call 方法

我们看到 Call 的实现主要是依赖 call 方法, call 方法刚开始的逻辑是这样的:

func (v Value) call(op string, in []Value) []Value {
// 不管 v.type 是啥,先转成 funcType,反正可以转
	t := (*funcType)(unsafe.Pointer(v.typ))
	var (
		fn       unsafe.Pointer
		rcvr     Value
		rcvrtype *rtype
	)
	// v 其实是接收者
	if v.flag&flagMethod != 0 {
		rcvr = v
		rcvrtype, t, fn = methodReceiver(op, v, int(v.flag)>>flagMethodShift)
	} else if v.flag&flagIndir != 0 {
		fn = *(*unsafe.Pointer)(v.ptr)
	} else {
		fn = v.ptr
	}
复制代码

假如是使用 Method 方法获取到的 Value,需要 methodReceiver 方法处理一下。

func methodReceiver(op string, v Value, methodIndex int) (rcvrtype *rtype, t *funcType, fn unsafe.Pointer) {
	i := methodIndex
	if v.typ.Kind() == Interface {
		// 先转成 接口类型
		tt := (*interfaceType)(unsafe.Pointer(v.typ))
		if uint(i) >= uint(len(tt.methods)) {
			panic("reflect: internal error: invalid method index")
		}
		// 取接口里的 method
		m := &tt.methods[i]
		if !tt.nameOff(m.name).isExported() {
			panic("reflect: " + op + " of unexported method")
		}
		// 转成 iface
		iface := (*nonEmptyInterface)(v.ptr)
		if iface.itab == nil {
			panic("reflect: " + op + " of method on nil interface value")
		}
		// 动态类型的 type
		rcvrtype = iface.itab.typ
		// 动态类型指向的 fn 指针
		fn = unsafe.Pointer(&iface.itab.fun[i])
		// 静态类型的 funcType, 因为和动态类型的一样
		t = (*funcType)(unsafe.Pointer(tt.typeOff(m.typ)))
	} else {
		rcvrtype = v.typ
		// 可导出的方法集合
		ms := v.typ.exportedMethods()
		if uint(i) >= uint(len(ms)) {
			panic("reflect: internal error: invalid method index")
		}
		m := ms[i]
		if !v.typ.nameOff(m.name).isExported() {
			panic("reflect: " + op + " of unexported method")
		}
		ifn := v.typ.textOff(m.ifn)
		fn = unsafe.Pointer(&ifn)
		t = (*funcType)(unsafe.Pointer(v.typ.typeOff(m.mtyp)))
	}
	return
}

type method struct {
	name nameOff // name of method
	mtyp typeOff // method type (without receiver) 函数类型
	ifn  textOff // fn used in interface call (one-word receiver)
	tfn  textOff // fn used for normal method call
}
复制代码

methodReceiver 方法

methodReceiver 会获取对应 method 的类型、指针以及接收者的类型。

call 方法接着会做一些可变参数的判断以及组合传入的参数值,然后调用 runtime.call (实际是 runtime.reflectcall)

call(frametype, fn, args, uint32(frametype.size), uint32(retOffset))

上面的方法才会真正的调用函数。然后把返回值组合成 []Value 返回,返回值的组合逻辑主要涉及指针地址的位移操作,这里就不累述了。

Convert

func (v Value) Convert(t Type) Value {
	if v.flag&flagMethod != 0 {
		v = makeMethodValue("Convert", v)
	}
	op := convertOp(t.common(), v.typ)
	if op == nil {
		panic("reflect.Value.Convert: value of type " + v.typ.String() + " cannot be converted to type " + t.String())
	}
	return op(v, t)
}
复制代码

Value 的 Convert 函数

表示 v 转成类型为 t 的 Valuereflect.TypeOf().ConvertibleTo 也是调用 convertOp 来实现的。包括 brommodel下字段的反射赋值也是调用的这个方法。但是这个方法有在数字转成字符串的时候会有问题。

func convertOp(dst, src *rtype) func(Value, Type) Value {
	switch src.Kind() {
	case Int, Int8, Int16, Int32, Int64:
		switch dst.Kind() {
		case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
			return cvtInt
		case Float32, Float64:
			return cvtIntFloat
		case String:
			return cvtIntString
		}

	case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
		switch dst.Kind() {
		case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
			return cvtUint
		case Float32, Float64:
			return cvtUintFloat
		case String:
			return cvtUintString
		}

	case Float32, Float64:
		switch dst.Kind() {
		case Int, Int8, Int16, Int32, Int64:
			return cvtFloatInt
		case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
			return cvtFloatUint
		case Float32, Float64:
			return cvtFloat
		}

	case Complex64, Complex128:
		switch dst.Kind() {
		case Complex64, Complex128:
			return cvtComplex
		}

	case String:
		if dst.Kind() == Slice && dst.Elem().PkgPath() == "" {
			switch dst.Elem().Kind() {
			case Uint8:
				return cvtStringBytes
			case Int32:
				return cvtStringRunes
			}
		}

	case Slice:
		if dst.Kind() == String && src.Elem().PkgPath() == "" {
			switch src.Elem().Kind() {
			case Uint8:
				return cvtBytesString
			case Int32:
				return cvtRunesString
			}
		}

	case Chan:
		if dst.Kind() == Chan && specialChannelAssignability(dst, src) {
			return cvtDirect
		}
	}

	// dst and src have same underlying type.
	if haveIdenticalUnderlyingType(dst, src, false) {
		return cvtDirect
	}

	// dst and src are non-defined pointer types with same underlying base type.
	if dst.Kind() == Ptr && dst.Name() == "" &&
		src.Kind() == Ptr && src.Name() == "" &&
		haveIdenticalUnderlyingType(dst.Elem().common(), src.Elem().common(), false) {
		return cvtDirect
	}

	if implements(dst, src) {
		if src.Kind() == Interface {
			return cvtI2I
		}
		return cvtT2I
	}

	return nil
}
复制代码

我们先看数字转成字符串,拿其中的 cvtIntString 看一下

func cvtIntString(v Value, t Type) Value {
	return makeString(v.flag.ro(), string(v.Int()), t)
}

func makeString(f flag, v string, t Type) Value {
	ret := New(t).Elem()
	ret.SetString(v)
	ret.flag = ret.flag&^flagAddr | f
	return ret
}
复制代码

这里直接把 v.Int() 转成了 string,很明显是错误的,在 go 中是不能把 int 直接转成 string 的,我们一般都是调用 strconv.Itoa() 转成十进制的数字。有兴趣的可以看一下 strconv.Itoa(),相当于把数字,一点一点拆成 string

其他类型的转换应该都是正常的,先判断类型能否转换,然后返回不同的转换函数

Elem

接下来看一下 Elem 方法,这个方法主要针对 ptrinterface

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() // 害怕在类型转换的时候转成 eface,丢失了一些类型信息
			})(v.ptr))
		}
		x := unpackEface(eface)
		if x.flag != 0 {
			x.flag |= v.flag.ro()
		}
		// interface 实际的类型
		return x
	case Ptr:
		ptr := v.ptr
		// 指针的指针, 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
		// 更改 flag
		fl := v.flag&flagRO | flagIndir | flagAddr
		fl |= flag(typ.Kind())
		return Value{typ, ptr, fl}
	}
	panic(&ValueError{"reflect.Value.Elem", v.kind()})
}
复制代码

我们假如类型为 ptr 的逻辑:

  • 先转成 ptrType
  • ptrType.elem 元素类型作为新 Value 的类型,我们知道 elem 指向的是元素静态类型
  • 然后 flag 位或上一些标志位

这里有一个 if 判断,if v.flag&flagIndir != 0 {ptr = *(*unsafe.Pointer)(ptr)} 我们没有说到。什么时候会进入到这个 if 逻辑里呢?

现在假如数据为指针的指针,然后调用 Elem 之后,flag 上就带上了 flagIndir 的标志位,我们发现只有 typflag 变了, ptr 没变,也就是说 ptr 现在还是指向指针的指针。假如现在把调用过 ElemValue 再调用一次,这个时候就会进入到上面的 if 逻辑块里了。

if 逻辑块里做了什么呢?把 ptr 转成指针的指针,然后取值,就获取到了指针。也就是说现在 ptr 是一个普通的指向值的指针了。记住这块逻辑,在 Value 装箱成 interface 的时候,我们会介绍到。

假如类型为 Interface 的逻辑:

  • 转成空的 interface,假如有函数,需要转成一个带 M() 方法的空 interface,这里应该是害怕丢失掉类型信息(只能这么解释了)
  • 把空的 interface 开箱成 Value,这里的 Value 就变成动态类型了

Interface

Interface 主要返回 Value 的实际的值

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

func valueInterface(v Value, safe bool) interface{} {
	if v.flag == 0 {
		panic(&ValueError{"reflect.Value.Interface", Invalid})
	}
	if safe && v.flag&flagRO != 0 {
		panic("reflect.Value.Interface: cannot return value obtained from unexported field or method")
	}
	if v.flag&flagMethod != 0 {
		v = makeMethodValue("Interface", v)
	}

	if v.kind() == Interface {
		// Special case: return the element inside the interface.
		// Empty interface has one layout, all interfaces with
		// methods have a second layout.
		if v.NumMethod() == 0 {
			return *(*interface{})(v.ptr)
		}
		return *(*interface {
			M()
		})(v.ptr)
	}
	return packEface(v)
}
复制代码

主要是调用 packEface 方法,把 Value 装箱成 Interface

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 := 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:
		// Value is direct, and so is the interface. 就是个指针
		e.word = v.ptr
	}
	e.typ = t
	return i
}
复制代码

假如是间接地址:

  • 可寻址,new 一个新指针,把 ptr 的值拷贝到新指针,然后赋值给 e.word。更改返回的 interface 不会影响到原值
  • 不可寻址,说明就是个值复本,直接赋值 e.word

直接地址,并且标志位里含有 flagIndir 间接标志:

  • 转成指针的指针,然后取值成指针,赋值给 e.word

这里有一个问题,直接地址的类型,比如 map。我们声明一个 map 的指针指针 A,然后对 AB:=reflect.ValueOf(A).Elem().Elem() 操作,根据 Elem 代码,我们了解到现在的 B 的标志位里有 flagIndir 标志,并且 B 现在的 ptr 是指向 map 的指针,因为第二次 Elem 的时候,已经把指针的指针转成指针了。然后我们调用 B.Interface() ,这个时候我们发现:

  • Bmap ,是直接类型
  • B的标志位里有 flagIndir 间接标志
  • 执行 e.word = *(*unsafe.Pointer)(v.ptr) 操作

但是 B.ptr 是指针,虽然 unsafe.Pointer 可以转成任意的指针,但是对指针按照指针的指针来操作,获取到的指针地址肯定是会变的。

但是!但是!这块代码可以正常执行!!也就是说这块代码最终指向了正确的 map

那我们就猜测一下,对于直接类型,指针是可以转成指针的指针的。但是下面的单测却 panic 了,没法证明这个逻辑:

	a := map[string]string{"cc": "dd"}
	c := unsafe.Pointer(&a)
	fmt.Println(c)

	d := *(*unsafe.Pointer)(c)
	fmt.Println(d)

	f := *(*map[string]string)(d)
	fmt.Println(f) 
复制代码

这是一块俺没法弄明白的逻辑!

文章分类
后端
文章标签