Golang反射源码分析

news2024/11/15 8:53:53

在go的源码包及一些开源组件中,经常可以看到reflect反射包的使用,本文就与大家一起探讨go反射机制的原理、学习其实现源码

首先,了解一下反射的定义:

反射是指计算机程序能够在运行时,能够描述其自身状态或行为、调整或修改其状态或行为的能力。

具体到go的反射机制,对应为:

go提供了在运行时检查变量的值、更新变量的值和调用它们的方法的机制,而在编译时并不知道这些变量的具体类型

接口

反射与Interface息息相关,反射是通过Interface的类型信息实现的,为更方便学习go反射机制,先复习一下Interface相关知识

看一下空接口Interface的结构定义

//runtime/runtime2.go
type eface struct {
    _type *_type
    data  unsafe.Pointer
}

data表示指向数据的指针,_type是所有类型的公共描述信息

_type类型结构的具体定义为

//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                       
}

其中size是类型的大小,hash是类型的哈希值;tflag是类型的tag信息,与反射相关,align和fieldalign与内存对齐相关,kind是类型编号,具体定义位于runtime/typekind.go中,gcdata是gc相关信息

通过空接口的定义可以看出,变量在转换为Interface时,接口值保存了变量的类型及指针两种信息,通过类型信息的对应实现接口与变量之间的互相转换。

go反射三定律

go反射机制在使用时可以总结为以下三定律

  1. 反射可以将“接口类型变量”转换为“反射类型对象”
  2. 反射可以将“反射类型对象”转换为“接口类型变量”
  3. 如果要修改“反射类型对象”,其值必须是“可写的”(settable)

接下来就结合这三个定律来学习reflect的源码

定律一

反射可以将“接口类型变量”转换为“反射类型对象”

反射类型对象有两种,分别是Type和Value,Type类型对应一个变量的类型信息,而Value类型则对应一个变量的值信息

Type

Type为一个接口类型,接口包含了所有可对类型信息执行的操作。其中可导出的方法偶尔会在一些源码包里看到,而未导出的方法只在内部使用,common方法返回类型基本信息,uncommon方法返回保留类型实现方法的uncommonType类型。

type Type interface {
    Align() int
    FieldAlign() int
    Method(int) Method
    MethodByName(string) (Method, bool)
    NumMethod() int
    Name() string
    String() string
    Field(i int) StructField
    FieldByName(name string) (StructField, bool)
    //....
    common() *rtype
    uncommon() *uncommonType
}

反射包通过reflect.Typeof()方法返回Type类型,下面是方法的实现

// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
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
}

方法很简短,第一步将空接口类型强转为emptyInterface类型,第二步,toType取出其中typ字段。这个函数的关键在于第一步的类型转换,前面复习了空接口eface的定义,再来看一下emptyInterface的定义以及其中typ字段的定义

// emptyInterface is the header for an interface{} value.
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
    alg        *typeAlg // algorithm table
    gcdata     *byte    // garbage collection data
    str        nameOff  // string form
    ptrToThis  typeOff  // type for pointer to this type, may be zero
}

很清晰发现这两个结构完全相同,使用unsafe.Poniter做类型转换,也就取出了变量的类型信息及地址

reflect.Typeof方法最终返回了包含变量类型信息的rtype类型的指针,也就是表示,rtype实现了Type接口的所有方法,这些方法分为通用方法和类型特有方法两类

// 通用方法
func (t *rtype) String() string // 获取 t 类型的字符串描述,不要通过 String 来判断两种类型是否一致。

func (t *rtype) Name() string // 获取 t 类型在其包中定义的名称,未命名类型则返回空字符串。

func (t *rtype) PkgPath() string // 获取 t 类型所在包的名称,未命名类型则返回空字符串。

func (t *rtype) Kind() reflect.Kind // 获取 t 类型的类别。

func (t *rtype) Size() uintptr // 获取 t 类型的值在分配内存时的大小,功能和 unsafe.SizeOf 一样。

func (t *rtype) Align() int  // 获取 t 类型的值在分配内存时的字节对齐值。

func (t *rtype) FieldAlign() int  // 获取 t 类型的值作为结构体字段时的字节对齐值。

func (t *rtype) NumMethod() int  // 获取 t 类型的方法数量。

func (t *rtype) Method() reflect.Method  // 根据索引获取 t 类型的方法,如果方法不存在,则 panic。
// 如果 t 是一个实际的类型,则返回值的 Type 和 Func 字段会列出接收者。
// 如果 t 只是一个接口,则返回值的 Type 不列出接收者,Func 为空值。

func (t *rtype) MethodByName(string) (reflect.Method, bool) // 根据名称获取 t 类型的方法。

func (t *rtype) Implements(u reflect.Type) bool // 判断 t 类型是否实现了 u 接口。

func (t *rtype) ConvertibleTo(u reflect.Type) bool // 判断 t 类型的值可否转换为 u 类型。

func (t *rtype) AssignableTo(u reflect.Type) bool // 判断 t 类型的值可否赋值给 u 类型。

func (t *rtype) Comparable() bool // 判断 t 类型的值可否进行比较操作
// 注意对于:数组、切片、映射、通道、指针、接口 
func (t *rtype) Elem() reflect.Type // 获取元素类型、获取指针所指对象类型,获取接口的动态类型

// 数组
func (t *rtype) Len() int  // 获取数组的元素个数

// Map
func (t *rtype) Key() reflect.Type // 获取映射的键类型

// 通道
func (t *rtype) ChanDir() reflect.ChanDir // 获取通道的方向

// 结构体
func (t *rtype) NumField() int  // 获取字段数量

func (t *rtype) Field(int) reflect.StructField  // 根据索引获取字段

func (t *rtype) FieldByName(string) (reflect.StructField, bool)  // 根据名称获取字段

func (t *rtype) FieldByNameFunc(match func(string) bool) (reflect.StructField, bool)  // 根据指定的匹配函数 math 获取字段

func (t *rtype) FieldByIndex(index []int) reflect.StructField  // 根据索引链获取嵌套字段

// 函数
func (t *rtype) NumIn() int // 获取函数的参数数量

func (t *rtype) In(int) reflect.Type // 根据索引获取函数的参数信息

func (t *rtype) NumOut() int // 获取函数的返回值数量

func (t *rtype) Out(int) reflect.Type // 根据索引获取函数的返回值信息

func (t *rtype) IsVariadic() bool  // 判断函数是否具有可变参数。
// 如果有可变参数,则 t.In(t.NumIn()-1) 将返回一个切片。

在这些方法中,以String()、Field()、NumMethod()分别为例探究一下源码的实现

String()方法返回类型名称,当fmt输出时以%T格式输出变量类型名称,取的也是本方法的结果

rtype的str字段为类型名字偏移量,nameoff方法根据str偏移量转为name类型,最后调用name类型的name方法返回[]byte类型

func (t *rtype) String() string {
    s := t.nameOff(t.str).name()
    if t.tflag&tflagExtraStar != 0 {
        return s[1:]
    }
    return s
}
func (t *rtype) nameOff(off nameOff) name {
    return name{(*byte)(resolveNameOff(unsafe.Pointer(t), int32(off)))}
}
//resolveNameOff在/runtime/type.go中实现
func resolveNameOff(ptrInModule unsafe.Pointer, off nameOff) name {
    if off == 0 {
        return name{}
    }
    base := uintptr(ptrInModule)
    for md := &firstmoduledata; md != nil; md = md.next {
        if base >= md.types && base < md.etypes {
        res := md.types + uintptr(off)
            if res > md.etypes {
                println("runtime: nameOff", hex(off), "out of range", hex(md.types), "-", hex(md.etypes))
                throw("runtime: name offset out of range")
            }
            return name{(*byte)(unsafe.Pointer(res))}
        }
    }
    // No module found. see if it is a run time name.
    reflectOffsLock()
    res, found := reflectOffs.m[int32(off)]
    reflectOffsUnlock()
    if !found {
        println("runtime: nameOff", hex(off), "base", hex(base), "not in ranges:")
        for next := &firstmoduledata; next != nil; next = next.next {
            println("\ttypes", hex(next.types), "etypes", hex(next.etypes))
        }
        throw("runtime: name offset base pointer out of range")
    }
    return name{(*byte)(res)}
}

nameoff方法根据rtype类型的name偏移量str字段,得到名称*byte的全局地址,再根据name结构的方法解析格式,得出name、tag、pkgPath等信息。

// name is an encoded type name with optional extra data.
// The first byte is a bit field containing:
//
//  1<<0 the name is exported
//  1<<1 tag data follows the name
//  1<<2 pkgPath nameOff follows the name and tag
//
// The next two bytes are the data length:
//
//   l := uint16(data[1])<<8 | uint16(data[2])
//
// Bytes [3:3+l] are the string data.
//
// If tag data follows then bytes 3+l and 3+l+1 are the tag length,
// with the data following.
type name struct {
     bytes *byte
}
//name结构方法,与runtime/type.go中一致
func (n name) nameLen() int
func (n name) tagLen() int
func (n name) name() (s string)
func (n name) tag() (s string)
func (n name) pkgPath() string

回过头来看runtime中resolveNameOff方法,firstmoduledata是一个全局的变量,保存了编译过程中所有类型的名称、类型等信息,与nameoff类型的str字段相同,rtype中typeOff类型的ptrToThis为类型信息偏移量,查找方法逻辑同resolveNameOff

下面通过一小段代码来验证类型和名称信息是全局唯一的,采用的方法同反射中以emptyInterface结构做类型转换一样

由于TypeOf返回为接口类型,不便于操作,我们以Value类型为例,Value类型包含一个rtype类型字段

type TestType struct {
    Name string
    Age  int
}
type value struct {
    typ  *ltype
    ptr  unsafe.Pointer
    flag uintptr
}
//ltype字段定义同rtype字段
type ltype struct {
    size       uintptr
    ptrdata    uintptr
    hash       uint32
    tflag      uint8
    align      uint8
    fieldAlign uint8
    kind       uint8
    alg        *int
    gcdata     *byte
    str        int32
    ptrToThis  int32
}
func main() {
    var a TestType
    var b TestType
    valueA := reflect.ValueOf(a)
    valueB := reflect.ValueOf(b)
    usA := (*value)(unsafe.Pointer(&valueA))
    usB := (*value)(unsafe.Pointer(&valueB))
    fmt.Printf("A.str:%X, B.str:%X\n", usA.typ.str, usB.typ.str)
    fmt.Printf("A.ptrToThis:%X, B.ptrToThis:%X\n", usA.typ.ptrToThis, usB.typ.ptrToThis)
    fmt.Printf("a.typ:%p, b.typ:%p\n", usA.typ, usB.typ)
}

通过value类型转换,分别取出a、b两个变量类型信息,再分别打印出a和b的name偏移量、类型偏移量,类型结构指向地址

来看一下结果,验证是否为全局信息

A.str:6206, B.str:6206
A.ptrToThis:B320, B.ptrToThis:B320
a.typ:0x4a5360, b.typ:0x4a5360

所以,编码过程中用到的所有变量,在编译构建过程中,已经将所有的类型信息保存在全局空间内,生成反射对象、接口类型转化等过程都是在取这一些类型信息

继续研究下一个Field方法

Field方法是struct类型的特有方法,方法首先就判断了类型,类型不匹配,直接panic

func (t *rtype) Field(i int) StructField {
    if t.Kind() != Struct {
        panic("reflect: Field of non-struct type")
    }
    tt := (*structType)(unsafe.Pointer(t))
    return tt.Field(i)
}
//与runtime/type.go中定义同步
type structType struct {
    rtype
    pkgPath name
    fields  []structField // sorted by offset
}
// Field方法返回第i个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
}

方法关键的第一步,将rtype指针转为structType类型指针,structType类型第一个字段rtype为类型的基本信息,后面两个字段为类型特有信息,最终通过sturctType类型的Field方法返回StructField类型

再来看一下其他类型结构,其第一个字段均为rtype类型,且与runtime/type.go中定义同步

type sliceType struct {
    rtype
    elem *rtype // slice element type
} 
type ptrType struct { 
    rtype
    elem *rtype // pointer element (pointed at) type
}
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)
}

对于特殊类型,通用类型信息指针*rtype其实是执行其完整类型信息,只是再TypeOf时只取出了第一个字段

还是用一小段代码验证一下structType类型的转换

type TestType struct {
    Name string
    Age  int
}
type name struct {
    bytes *byte
}
type structType struct {
    ltype
    pkgPath name
    fields  []structField
}
type structField struct {
    name        name
    typ         *ltype
    offsetEmbed uintptr
}
func main() {
    var a TestType
    valueA := reflect.ValueOf(a)
    usA := (*value)(unsafe.Pointer(&valueA))
    stA := (*structType)(unsafe.Pointer(usA.typ))
    fmt.Printf("field length:%d\n", len(stA.fields))
    fmt.Printf("field 0 kind:%b\n", stA.fields[0].typ.kind)
    fmt.Printf("field 1 kind:%b\n", stA.fields[1].typ.kind)
}

再来验证一下结果,与TestType类型是否一致,字段数量2,第一个字段类型2值为24,类型为string,第二个字段类型值为2,类型为int,完全一致

field length:2
field 0 kind:11000
field 1 kind:10000010

最后是第三个方法NumMethod

Type接口的NumMethod方法返回方法数量,首先判断了是否是Interface类型,是调用Interface类型的NumMethod方法,与Field类似

func (t *rtype) NumMethod() int { 
    if t.Kind() == Interface {
        tt := (*interfaceType)(unsafe.Pointer(t))
        return tt.NumMethod()
    }
    return len(t.exportedMethods())
}
func (t *rtype) exportedMethods() []method {
    ut := t.uncommon()
    if ut == nil {
        return nil 
    }   
    return ut.exportedMethods()
}
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
}
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类型位置,调用exportedMethods方法取出method信息

这个方法的关键在于uncommon,看一下uncommon方法的源码

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
    }
}

每种类型的处理方法都是一样的,定义一个临时类型,第一个字段为类型信息,第二个字段为uncommonType类型。说明所有实现了方法的类型,其类型信息的地址后都紧跟着一个uncommonType类型,这个类型里维护相关方法信息

同样的,一段代码验证一下

func (t TestType) Func1(in string) error {
    return nil
}
func (t TestType) Func2() {
    return
}
type u struct {
    structType
    u uncommonType
}
type uncommonType struct {
    pkgPath int32
    mcount  uint16
    xcount  uint16
    moff    uint32
    _       uint32
}
func main() {
    var a TestType
    valueA := reflect.ValueOf(a)
    usA := (*value)(unsafe.Pointer(&valueA))
    ucA := (*u)(unsafe.Pointer(usA.typ))
    fmt.Printf("uncommonType mcount:%d\n", ucA.u.mcount)
    fmt.Printf("uncommonType moff:%d\n", ucA.u.moff)
}

对照一下验证结果

uncommonType mcount:2
uncommonType moff:64

mcount表示方法类型,moff方法信息偏移量,是64而不是16,所以可推论方法信息不是紧接着uncommonType排列的

简单来做第一个总结,所有类型的类型信息均在编译时确定,并保存在全局变量中,这些类型信息的排列如图:

222

可以通过通用类型指针,取到其类型特有信息,类型方法信息等

Value

Value是一个类型,通过reflect方法ValueOf方法返回,Value是存放类型值信息的结构,先来看一下定义

type Value struct {
    typ *rtype
    // Pointer-valued data or, if flagIndir is set, pointer to data.
    // Valid when either flagIndir is set or typ.pointers() is true.
    ptr unsafe.Pointer
    // flag holds metadata about the value.
    // The lowest bits are flag bits:
    //  - flagStickyRO: obtained via unexported not embedded field, so read-only
    //  - flagEmbedRO: obtained via unexported embedded field, so read-only
    //  - flagIndir: val holds a pointer to the data
    //  - flagAddr: v.CanAddr is true (implies flagIndir)
    //  - flagMethod: v is a method value.
    // The next five bits give the Kind of the value.
    // This repeats typ.Kind() except for method values.
    // The remaining 23+ bits give a method number for method values.
    // If flag.kind() != Func, code can assume that flagMethod is unset.
    // If ifaceIndir(typ), code can assume that flagIndir is set.
    flag

相比Type接口的底层rtype不同,Value类型多了两个字段,分别是ptr指向数据的指针,flag标识位,指针指向变量数据。而相比空接口类型,多了这个flag字段,flag是Value内部方法中的一个比较关键的数据,用在很多判断条件中

func ValueOf(i interface{}) Value {
    if i == nil {
        return Value{}
    }
    escapes(i)
    return unpackEface(i)
}
func unpackEface(i interface{}) Value {
    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 escapes(x interface{}) {
    if dummy.b {
        dummy.x = x
    }
}
var dummy struct {
    b bool
    x interface{}
} 

ValueOf方法还是通过接口类型转为包内的emptyInterface类型,取出变量类型信息和变量地址

Value提供以下方法,可分为取值操作、设置值操作、类型特有方法三类

//用于获取值方法
func (v Value) Int() int64 // 获取int类型值,如果 v 值不是有符号整型,则 panic。

func (v Value) Uint() uint64 // 获取unit类型的值,如果 v 值不是无符号整型(包括 uintptr),则 panic。

func (v Value) Float() float64 // 获取float类型的值,如果 v 值不是浮点型,则 panic。

func (v Value) Complex() complex128 // 获取复数类型的值,如果 v 值不是复数型,则 panic。

func (v Value) Bool() bool // 获取布尔类型的值,如果 v 值不是布尔型,则 panic。

func (v Value) Len() int // 获取 v 值的长度,v 值必须是字符串、数组、切片、映射、通道。

func (v Value) Cap() int  // 获取 v 值的容量,v 值必须是数值、切片、通道。

func (v Value) Index(i int) reflect.Value // 获取 v 值的第 i 个元素,v 值必须是字符串、数组、切片,i 不能超出范围。

func (v Value) Bytes() []byte // 获取字节类型的值,如果 v 值不是字节切片,则 panic。

func (v Value) Slice(i, j int) reflect.Value // 获取 v 值的切片,切片长度 = j - i,切片容量 = v.Cap() - i。
// v 必须是字符串、数值、切片,如果是数组则必须可寻址。i 不能超出范围。

func (v Value) Slice3(i, j, k int) reflect.Value  // 获取 v 值的切片,切片长度 = j - i,切片容量 = k - i。
// i、j、k 不能超出 v 的容量。i <= j <= k。
// v 必须是字符串、数值、切片,如果是数组则必须可寻址。i 不能超出范围。

func (v Value) MapIndex(key Value) reflect.Value // 根据 key 键获取 v 值的内容,v 值必须是映射。
// 如果指定的元素不存在,或 v 值是未初始化的映射,则返回零值(reflect.ValueOf(nil))

func (v Value) MapKeys() []reflect.Value // 获取 v 值的所有键的无序列表,v 值必须是映射。
// 如果 v 值是未初始化的映射,则返回空列表。

func (v Value) OverflowInt(x int64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是有符号整型。

func (v Value) OverflowUint(x uint64) bool  // 判断 x 是否超出 v 值的取值范围,v 值必须是无符号整型。

func (v Value) OverflowFloat(x float64) bool  // 判断 x 是否超出 v 值的取值范围,v 值必须是浮点型。

func (v Value) OverflowComplex(x complex128) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是复数型。

//设置值方法
func (v Value) SetInt(x int64)  //设置int类型的值

func (v Value) SetUint(x uint64)  // 设置无符号整型的值

func (v Value) SetFloat(x float64) // 设置浮点类型的值

func (v Value) SetComplex(x complex128) //设置复数类型的值

func (v Value) SetBool(x bool) //设置布尔类型的值

func (v Value) SetString(x string) //设置字符串类型的值

func (v Value) SetLen(n int)  // 设置切片的长度,n 不能超出范围,不能为负数。

func (v Value) SetCap(n int) //设置切片的容量

func (v Value) SetBytes(x []byte) //设置字节类型的值

func (v Value) SetMapIndex(key, val reflect.Value) //设置map的key和value,前提必须是初始化以后,存在覆盖、不存在添加

//其他方法
//结构体相关:
func (v Value) NumField() int // 获取结构体字段(成员)数量

func (v Value) Field(i int) reflect.Value  //根据索引获取结构体字段

func (v Value) FieldByIndex(index []int) reflect.Value // 根据索引链获取结构体嵌套字段

func (v Value) FieldByName(string) reflect.Value // 根据名称获取结构体的字段,不存在返回reflect.ValueOf(nil)

func (v Value) FieldByNameFunc(match func(string) bool) Value // 根据匹配函数 match 获取字段,如果没有匹配的字段,则返回零值(reflect.ValueOf(nil))

//chan相关:
func (v Value) Send(x reflect.Value)// 发送数据(会阻塞),v 值必须是可写通道。

func (v Value) Recv() (x reflect.Value, ok bool) // 接收数据(会阻塞),v 值必须是可读通道。

func (v Value) TrySend(x reflect.Value) bool // 尝试发送数据(不会阻塞),v 值必须是可写通道。

func (v Value) TryRecv() (x reflect.Value, ok bool) // 尝试接收数据(不会阻塞),v 值必须是可读通道。

func (v Value) Close() // 关闭通道

//函数相关
func (v Value) Call(in []Value) (r []Value) // 通过参数列表 in 调用 v 值所代表的函数(或方法)。函数的返回值存入 r 中返回。
// 要传入多少参数就在 in 中存入多少元素。
// Call 即可以调用定参函数(参数数量固定),也可以调用变参函数(参数数量可变)。

func (v Value) CallSlice(in []Value) []Value // 调用变参函数

定律二

反射可以将“反射类型对象”转换为“接口类型变量”

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", 0})
    }
    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 {
        if v.NumMethod() == 0 {
            return *(*interface{})(v.ptr)
        }
        return *(*interface {
            M()
        })(v.ptr)
    }
    // TODO: pass safe to packEface so we don't need to copy if safe==true?
    return packEface(v)
}
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
    }
    e.typ = t
    return i
}

packEface对应之前的unpackEface,完成空接口类型与反射变量类型的转换

方法比较容易理解,其中有个flagIndir的判断需要再深入理解下,先详细看一下Value的flag字段含义

    //  - flagStickyRO: obtained via unexported not embedded field, so read-only
    //  - flagEmbedRO: obtained via unexported embedded field, so read-only
    //  - flagIndir: val holds a pointer to the data
    //  - flagAddr: v.CanAddr is true (implies flagIndir)
    //  - flagMethod: v is a method value.

flagIndir是标识当前指针即通过空接口传进来的指针是实际数据还是指向数据的指针,标识是在ValueOf中设置的,判断依据也是ifaceIndir这个方法

func ValueOf(i interface{}) Value {
    //...
    if ifaceIndir(t) {
        f |= flagIndir
    }
    //...
}
func ifaceIndir(t *rtype) bool {
    return t.kind&kindDirectIface == 0
}
//flagIndir
flagIndir  flag = 1 << 7

rtype里kind字段为变量类型,变量类型小于1 << 5,

所以这个flagIndir是哪里定义?是什么逻辑

其定义位置如下

//compile/internal/gc/reflect.go:890
if isdirectiface(t) {
    i |= objabi.KindDirectIface
}

func isdirectiface(t *types.Type) bool {
    if t.Broke() {
        return false
    }
    switch t.Etype {
    case TPTR,
        TCHAN,
        TMAP,
        TFUNC,
        TUNSAFEPTR:
        return true
    case TARRAY:
        // Array of 1 direct iface type can be direct.
        return t.NumElem() == 1 && isdirectiface(t.Elem())
    case TSTRUCT:
        // Struct with 1 field of direct iface type can be direct.
        return t.NumFields() == 1 && isdirectiface(t.Field(0).Type)
    }
    return false
}

根据这个函数可以看出所有指针类型、只有一个元素且元素为指针类型的数组和struct均为direct类型,除此之外ValueOf的返回值Value中flag的flagIndir位均置为1

具体看一下packEface这个方法,按照三种case来分别举例说明

第一种Indirect类型,首先以int为例验证说明

//case ifaceIndir(t)
type value struct {
    typ  *ltype
    ptr  unsafe.Pointer
    flag uintptr
}
type eface struct {
    *ltype
    data unsafe.Pointer
}
func main() {
    a := 100
    aValue := reflect.ValueOf(a)
    ua := (*value)(unsafe.Pointer(&aValue))
    fmt.Printf("flag:%b\n", ua.flag)
    fmt.Printf("kind:%b\n", ua.typ.kind)
    fmt.Printf("before ptr:%p\n", &a)
    fmt.Printf("value ptr:%p\n", ua.ptr)
    aInterface := aValue.Interface()
    ia := (*eface)(unsafe.Pointer(&aInterface))
    fmt.Printf("after ptr:%p\n", ia.data)
}

输出结果为:

flag:10000010
kind:10000010
before ptr:0xc000018060
before ptr:0xc000018068
after ptr:0xc000018068

由flag和kind判断其类型为int,flagIndir位为1,进入第一个case。原变量地址为0x8060,转为空接口时,进行了变量复制,到了Value类型内地址变为0xx8068,同时flagAddr为为0,返回interface内指针指向Value内复制变量地址

下面例子验证flagAddr为1

type Test struct {
    Name *string
    Age  int
}
func main() {
    a := "test"
    c := 100
    b := Test{&a, c}
    bValue := reflect.ValueOf(&b)
    nAge := bValue.Elem().Field(1)
    un := (*value)(unsafe.Pointer(&nAge))
    fmt.Printf("flag:%b\n", un.flag)
    fmt.Printf("kind:%b\n", un.typ.kind)
    fmt.Printf("before ptr age:%p\n", &b.Age)
    fmt.Printf("before ptr test:%p\n", &b)
    fmt.Printf("value ptr:%p\n", un.ptr)
    nInterface := nAge.Interface()
    in := (*eface)(unsafe.Pointer(&nInterface))
    fmt.Printf("after ptr:%p\n", in.data)
}

输出结果为:

flag:110000010
kind:10000010
before ptr age:0xc0000101f8
before ptr test:0xc0000101f0
value ptr:0xc0000101f8
after ptr:0xc000018080

由flag看出,其flagIndir与flagAddr均置为1,进入flagAddr条件判断内,返回interface底层数据指向新创建的类型地址,其内容一致。由于第一步传入为Test指针,故test地址为0x01f0,其Age字段为0x01f0,看到value内的地址为0x01f8,最终进行变量复制,返回复制新地址为0x8080

第二种Value为Indirect类型,返回interface为direct类型,以下例说明

func main() {
    a := "test"
    c := 100
    b := Test{&a, c}
    bValue := reflect.ValueOf(b)
    nName := bValue.Field(0)
    un := (*value)(unsafe.Pointer(&nName))
    fmt.Printf("flag:%b\n", un.flag)
    fmt.Printf("kind:%b\n", un.typ.kind)
    fmt.Printf("before ptr a:%p\n", &a)
    fmt.Printf("before ptr test:%p\n", &b)
    fmt.Printf("value ptr:%p\n", un.ptr)
    nInterface := nName.Interface()
    in := (*eface)(unsafe.Pointer(&nInterface))
    fmt.Printf("after ptr:%p\n", in.data)
}

输出结果为:

flag:10010110
kind:110110
before ptr a:0xc0000101e0
before ptr test:0xc0000101f0
value ptr:0xc000010200
after ptr:0xc0000101e0

由kind看出flagIndir为0,且flag中flagIndir为1,进入第二个case。原变量a指针地址为0x01e0,test结构地址为0x01f0,转为Value后,test结构地址变为0x0200,最终返回interface内指针指向地址,为0x0200地址内指针数据,指向原变量a地址

最终可以总结出Value类型内数据指针根据传入类型是否Indirect,可能是原数据,或者是原数据复制变量的指针,最终调用Interface方法,返回带数据的空接口类型时,其指针一定会转化为指向一个复制变量中,避免与通过反射更新原数据互相影响

接着做出第二个总结,关于reflect.Type、reflect.Value、interface{}三者的转化关系,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EcCeApBu-1678360708936)(/Users/zsh/Desktop/111.png)]

定律三

如果要修改“反射类型对象”,其值必须是“可写的”(settable)

反射对象是否可写同样是由flag来决定的,其判断位有两个,flagAddr和flagRO

可以通过CanSet方法进行判断,其值是否是settable

func (v Value) CanSet() bool {
    return v.flag&(flagAddr|flagRO) == flagAddr
}

由代码可以看出,只有flagAddr设置,而flagRO未设置时,value可设置

接下来再来看一下这两个字段值以及如何设置

const (
    flagStickyRO    flag = 1 << 5
    flagEmbedRO     flag = 1 << 6
    flagAddr        flag = 1 << 8
    flagRO          flag = flagStickyRO | flagEmbedRO
)

flagRO包含两种只读,flagStickyRO和flagEmbedRO,均是未导出只读类型

先来看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 {
        }
        return x
    case Ptr:
        ptr := v.ptr
        if v.flag&flagIndir != 0 {
            ptr = *(*unsafe.Pointer)(ptr)
        }
        if ptr == nil {
            return Value{}
        }
        tt := (*ptrType)(unsafe.Pointer(v.typ))
        typ := tt.elem
        //设置flagAddr
        fl := v.flag&flagRO | flagIndir | flagAddr
        fl |= flag(typ.Kind())
        return Value{typ, ptr, fl}
    }
    panic(&ValueError{"reflect.Value.Elem", v.kind()})
}        

flagAddr在Elem()方法时set,即只有对指针类型调用Elem方法后才可以进行赋值操作

一段代码进行验证

func main() {
    i := 1
    v := reflect.ValueOf(i)
    vPtr := reflect.ValueOf(&i)
    elem := vPtr.Elem()
    fmt.Printf("value of i:%v\n", v.CanSet())
    fmt.Printf("value of &i:%v\n", vPtr.CanSet())
    fmt.Printf("value of &i elem:%v\n", elem.CanSet())
    elem.SetInt(10) 
    fmt.Println(i)
}

输出结果为

value of i:false
value of &i:false
value of &i elem:true
10

Elem方法代表对指针变量进行解引用,即对应*arg这种操作,设置值操作可以理解为以下代码

func main() {
    i := 1
    v := &i
    *v = 10
}

flagRO针对结构体内field,其设置也是在Field方法内,参考代码

func (v Value) Field(i int) Value {
    if v.kind() != Struct {
        panic(&ValueError{"reflect.Value.Field", v.kind()})
    }
    tt := (*structType)(unsafe.Pointer(v.typ))
    if uint(i) >= uint(len(tt.fields)) {
        panic("reflect: Field index out of range")
    }
    field := &tt.fields[i]
    typ := field.typ
    // Inherit permission bits from v, but clear flagEmbedRO.
    fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind())
    // Using an unexported field forces flagRO.
    if !field.name.isExported() {
        if field.embedded() {
            fl |= flagEmbedRO
        } else {
            fl |= flagStickyRO
        }
    }
    ptr := add(v.ptr, field.offset(), "same as non-reflect &v.field")
    return Value{typ, ptr, fl}
}

通过代码来验证下flag设置

type TestStruct struct {
    string
    Name     string
    age      int
    City     *string
    province string
}
func main() {
    var test TestStruct
    test.age = 100
    test.Name = "test"
    temp := "guangzhou"
    test.City = &temp
    test.province = "guangdong"
    elem := reflect.ValueOf(&test).Elem()
    for i := 0; i < elem.NumField(); i++ {
        field := elem.Field(i)
        un := (*value)(unsafe.Pointer(&field))
        fmt.Printf("field index:%d, value:%v, flag:%b, canset:%v\n", i, field, un.flag, field.CanSet())
    }
}

输出结果为:

field index:0, value:, flag:111011000, canset:false
field index:1, value:test, flag:110011000, canset:true
field index:2, value:100, flag:110100010, canset:false
field index:3, value:0xc0000101e0, flag:110010110, canset:true
field index:4, value:guangdong, flag:110111000, canset:false

flagAddr为1<<8,flagRO为1<<5和1<<6,对照上面输出结果,CanSet返回结果与flag对应正确

调用Set、SetInt、SetFloat等方法进行值的更新,在set之前会进行settable的验证,如果不可set,会产生panic。所有一定在完全确认Value是settable之后,再调用方法

总结

  • 反射对象类型有Type和Value两种,TypeOf和ValueOf方法入参均为interface{}类型,依赖空接口类型中类型信息及变量指针实现所有反射功能
  • 类型信息维护在全局变量中,由编译时确定,类型信息包括基本类型信息、特定类型信息、类型方法信息三部分连续排列
  • Type、Value和interface{}之间可以进行互相转换
  • 利用反射类型进行更新操作,需先调用Elem()方法,并确保Value是settable

思考

反射功能强大,为什么不推荐使用反射?

  • 性能:new对象多,且堆上分配。如Value类型Elem()、Field()方法返回新的Value类型,均会创建新的Value
  • 可控性:一部分反射方法基于固定类型实现,传入类型不匹配,就会panic,对新手不友好

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/399702.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智慧赋能,聚力开源——第四届OpenI/O 启智开发者大会开源治理专场顺利举办!

为汇聚国内外知名开源组织共同探讨中国开源生态建设及开源治理相关议题&#xff0c;推进产学研用开源合作&#xff0c;2月24日下午&#xff0c;第四届OpenI/O启智开发者大会在深圳人才研修院智汇中心举办以“构建开源联合体&#xff0c;共建开源生态”为主题的开源治理专场分论…

C++基础了解-17-C++日期 时间

C日期 & 时间 一、C日期 & 时间 C 标准库没有提供所谓的日期类型。C 继承了 C 语言用于日期和时间操作的结构和函数。为了使用日期和时间相关的函数和结构&#xff0c;需要在 C 程序中引用 头文件。 有四个与时间相关的类型&#xff1a;clock_t、time_t、size_t 和 …

opencv识别车道线(霍夫线变换)

目录1、前言2、霍夫线变换2.1、霍夫线变换是什么&#xff1f;2.2、在opencv中的基本用法2.2.1、HoughLinesP函数定义2.2.2、用法3、识别车道3.1、优化3.1.1、降噪3.1.2、过滤方向3.1.3、截选区域3.1.4、测试其它图片图片1图片2图片31、前言 最近学习opencv学到了霍夫线变换&am…

ruoyi对接CAS统一身份认证

暂定逻辑如下&#xff1a;搭建CAS服务器端&#xff1a;项目地址&#xff1a;https://gitee.com/weigang_wu/cas-server-webapp.git项目里有二开的说明文档&#xff0c;如&#xff1a;按照自定义的数据库校验修改如下&#xff1a;首先&#xff1a;修改数据库连接以及查询数据这里…

博客系统(前后端分离版)

博客系统的具体实现 文章目录博客系统的具体实现软件开发的基本流程具体实现的八大功能数据库设计创建数据库操作数据库引入依赖封装DataSource创建实体类将JDBC增删改查封装起来实现博客列表页web.xml的配置文件实现博客系统的展示功能登录功能强制要求用户登录显示用户信息退…

求职复盘:干了四年外包出来,面试5次全挂

我的情况 大概介绍一下个人情况&#xff0c;男&#xff0c;毕业于普通二本院校非计算机专业&#xff0c;18年跨专业入行测试&#xff0c;第一份工作在湖南某软件公司&#xff0c;做了接近4年的外包测试工程师&#xff0c;今年年初&#xff0c;感觉自己不能够再这样下去了&…

为什么做知识管理,就想选择Baklib呢?

随着科技的不断发展&#xff0c;知识管理已经成为现代企业不可或缺的一个重要组成部分。由于信息化快速发展&#xff0c;企业每天都会产生大量的数据和信息&#xff0c;如何高效地获取、整理和利用这些信息已经成为了企业成功的关键因素之一。为了更好地管理企业知识&#xff0…

利用Iptables构建虚拟路由器

利用Iptables构建虚拟路由器 &#xff08;1&#xff09;修改网络类型 在VMware Workstation软件中选择“编辑→虚拟网络编辑器”菜单命令&#xff0c;在虚拟网络列表中选中VMnet1&#xff0c;将其配置为“仅主机模式&#xff08;在专用网络内连接虚拟机&#xff09;”&#x…

模板进阶(仿函数,特化等介绍)

非类型模板参数 模板参数有类型形参和非类型形参&#xff1b; 类型形参&#xff1a;使用typename或者class修饰的参数类型名称 非类型形参&#xff1a;一个普通常量作为模板参数形参&#xff0c;不能为浮点数&#xff0c;字符类型以及类对象&#xff1b; #include<iostrea…

虹科新品| HK-TrueNAS企业存储

一、HK-TrueNAS概述HK-TrueNAS 是一种统一存储阵列&#xff0c;提供混合和全闪存配置&#xff0c;以前所未有的价格提供全面的功能集和高达 10.5PB 的容量。TrueNAS 全闪存存储阵列为以闪存为中心的数据中心提供了理想的统一数据存储。每个混合和全闪存 TrueNAS 系统都使用 Tru…

VSCode 开发配置,一文搞定(持续更新中...)

一、快速生成页面骨架 文件 > 首选项 > 配置用户代码片段 选择需要的代码片段或者创建一个新的&#xff0c;这里以 vue.json 举例&#xff1a; 下面为我配置的代码片段&#xff0c;仅供参考&#xff1a; {"Print to console": {"prefix": "…

Mac系统配置java、Android_sdk、gradle、maven、ndk、flutter、tomcat环境变量

搞了三天&#xff0c;终于搞定MAC系统下的各种环境变量了…… 旧版本10.13.6或者更老的MAC系统&#xff0c;只用在.bash_profile文件编辑就行了&#xff1b;新版本10.14.2、10.15.7或者更高的&#xff0c;还要去.zshrc文件加一句source ~/.bash_profile&#xff0c;才能使所有…

java明文数据加密、脱敏方法总结

前言 在一些安全性要求比较高的项目里&#xff0c;避免不了要对敏感信息进行加解密&#xff0c;比如配置文件中的敏感信息。 第一种方法&#xff08;自定义加解密&#xff09; 加解密工具类&#xff1a; public class SecurityTools {public static final String ALGORITHM…

最新!Windows 11 更新将整合 AI 技术

微软MVP实验室研究员张雅琪&#xff08;阿法兔&#xff09;微软最有价值专家&#xff08;MVP&#xff09;&#xff0c;毕业于外交学院和香港大学&#xff0c;IT 技术社区创始人&#xff0c;中关村互联网金融研究院兼职研究员&#xff0c;多次受邀在微软 Reactor 进行公开演讲&a…

JS的BroadcastChannel与MessageChannel

BroadcastChannel与MessageChannel BroadcastChannel BroadcastChannel以广播的形式进行通信 BroadcastChannel用于创建浏览器标签页之间的通信 使用BroadcastChannel的浏览器标签页面必须要遵循同源策略 页面1使用BroadcastChannel创建一个频道&#xff0c;页面2使用Broadc…

latex入门指南:插入图片、表格、公式方法一览

省事链接&#xff1a; 生成表格latex代码&#xff1a;www.tablesgenerator.com 生成公式latex代码&#xff1a;www.latexlive.com 目录1 插入图片1.1 移动标题位置1.2 双栏文章中图片横跨双栏2 插入表格2.1 常规表格2.2 设置单元格宽度2.3 合并单元格2.4 三线表2.5 移动标题位置…

脑机接口科普0018——前额叶切除手术

本文禁止转载&#xff01;&#xff01;&#xff01; 首先说明一下&#xff0c;前额叶切除手术&#xff0c;现在已经不允许做了。 其次&#xff0c;前额叶切除手术&#xff0c;发明这个手术的人居然还获得了诺贝尔奖。太过于讽刺。1949年的那次诺贝尔医学奖&#xff08;就是我…

打怪升级之发送单个UDP包升级版

目标 1.message的输入由edit_control进行&#xff0c;需要捕获输入。 2.用户的主机地址和发送地址不一样&#xff0c;需要分别设置并绑定。 设计RC外观 必备组件&#xff1a;主机IP与端口&#xff0c;从机IP与端口&#xff0c;消息框&#xff0c;发送&#xff0c;连接按钮。…

打卡小达人之路:Spring Boot与Redis GEO实现商户附近查询

在当今社会&#xff0c;定位服务已经成为了各种应用的重要组成部分&#xff0c;比如地图、打车、美食等应用。如何在应用中实现高效的附近商户搜索功能呢&#xff1f;传统的做法是将商户的经纬度信息存储在关系型数据库中&#xff0c;然后使用SQL查询语句实现附近商户搜索功能。…

Anaconda环境配置Python数据分析库Pandas的方法

本文介绍在Anaconda环境中&#xff0c;安装Python语言pandas模块的方法。 pandas模块是一个基于NumPy的开源数据分析库&#xff0c;提供了快速、灵活、易用的数据结构和数据分析工具。它的主要数据结构是Series和DataFrame&#xff0c;可以处理各种数据格式&#xff0c;如CSV、…