go反射原理

go反射原理

go反射原理

本文基于go1.13.15

1.go汇编

1.1 基本语法

go采用plan9的汇编器完成汇编,有下面几个重要的伪寄存器

  • FP: Frame pointer: 局部变量访问

  • PC: Program counter: 程序计数器

  • SB: Static base pointer: 全局变量访问

  • SP: Stack pointer: 存储栈顶指针

常用指令如下

// 加括号代表是指针的引用 
MOVQ (AX), BX   // => BX = *AX 将AX指向的内存区域8byte赋值给BX 
MOVQ 16(AX), BX // => BX = *(AX + 16) 
// 不加括号是值的引用 
MOVQ AX, BX     // => BX = AX 将AX中存储的内容赋值给BX,注意区别

LEAQ 96(SP), BP // => BP = (SP+96)
LEAQ go.string."sj"(SB), CX // => CX = &("sj")

JEQ xxx //如果前面的判断相等则跳转到xxx
JNQ xxx //如果前面的判断不相等则跳转到xxx

CMPQ a,b //比较a和b

SUBQ $104, SP // SP -= 104
ADDQ $104, SP // SP += 104

1.2 生成汇编

  • 链接前的代码

    go tool compile -S -N -l test1.go > result.txt

    • -S

      输出汇编结果

    • -N

      禁止编译器优化

    • -l

      禁用内联编译

使用go tool complie --help得到说明

  • 链接后的代码
    • go tool compile -N -l test.go
    • go tool objdump test.o

2.Java反射

以class对象为桥梁

2.1 内存分布

2.2 类加载

加载->链接->初始化

在加载阶段,主要进行如下步骤

  • 通过一个类的全限定名获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在堆中生成一个代表这个类的java.lang.Class对象,引用存放在栈,作为访问方法区中这个类的各种数据的入口

使用new创建一个对象时,

  • 方法区

会在方法区中创建一个instanceKlass,其中包含了类型的元信息,如方法表、常量池等

在堆区创建instanceOopDesc对象,其中包含instanceKlass对象的引用

存放instanceOopDesc对象的引用

Java的反射即以class对象为入口,访问类的****元信息

3.go接口

实现go中的接口不需要显式声明,只需要实现接口中的全部方法,且实现的方法签名和接口中的完全一致即可

3.1 接收者

结构体实现接口 结构体指针实现接口
接收者为变量 T T
接收者为指针 T F

上表的解释

  • 接收者是指针类型的方法,很可能在方法中会对接收者的属性进行更改操作,从而影响接收者;而对于接收者是值类型的方法,在方法中不会对接收者本身产生影响
  • 如果方法的接收者是值类型,无论调用者是对象还是对象指针,修改的都是对象的副本(对于接收者为指针来说,会先解引用再拷贝对象),不影响调用者;如果方法的接收者是指针类型,则调用者修改的是指针指向的对象本身(对于接收者为变量来说,会先取址再拷贝指针)

3.2 运行时类型

go是强类型的语言,在编译期会进行类型检查,在编译后会对代码中出现的每一种引用类型创建相应的类型结构体变量用于存储****元信息

3.2.1 引用类型的运行时类型(runtime/type.go)

type _type struct {
   size       uintptr    //类型大小
   ptrdata    uintptr // size of memory prefix holding all pointers
   hash       uint32    //类型的hash
   tflag      tflag    //类型的flag,用于判断内部成员访问权限
   align      uint8    //对齐
   fieldalign uint8    //对齐
   kind       uint8    //类型的种类
   alg        *typeAlg    //类型使用的<hash,equal>函数对
   gcdata    *byte    //gc
   str       nameOff  //类型名称的字符串偏移量
   ptrToThis typeOff  
}
  • Interfacetype
type interfacetype struct {
   typ     _type    //接口类型
   pkgpath name    //接口包名
   mhdr    []imethod    //接口中的方法表
}
  • structtype
type structtype struct {
   typ     _type
   pkgPath name
   fields  []structfield
}

type structfield struct {
   name       name
   typ        *_type
   offsetAnon uintptr
}
  • ptrtype、functype、slicetype、chantype、arraytype、maptype、uncommontype

uncommontype是指向一个函数指针的数组,收集了这类型的实现的所有方法

3.2.2 eface(runtime/runtime2.go)

表示没有方法的接口,如interface{}

type eface struct {
   _type *_type    //类型
   data  unsafe.Pointer    //值指针
}

3.2.3 iface(runtime/runtime2.go)

表示有方法的接口

type iface struct {
   tab  *itab    //类型
   data unsafe.Pointer    //值指针
}
type itab struct {
   inter *interfacetype    //接口类型
   _type *_type    //实际类型
   hash  uint32     //_type的hash值拷贝,断言使用
   _     [4]byte    //对其填充8个字节使用
   fun   [1]uintptr // 接口中的方法调用入口,若有多个方法顺序向后排列在内存中
}
img

图中interfacetype和itab指向的是不同的_type对象

3.3 接口的构造

3.3.1 示例代码

type myInterface interface {
   test1(id int64) int64
   test2(flag bool)
}

type MyStruct struct {
   Id   int64
   Name string
}

func (m *MyStruct) test1(id int64) int64 {
   return id + m.Id
}

func (m *MyStruct) test2(flag bool) {
   fmt.Println(flag)
}

3.3.2 MyStruct

type."".MyStruct SRODATA size=144

          |--------- size -----| |-------ptrdata-------|
   0x0000 18 00 00 00 00 00 00 00  10 00 00 00 00 00 00 00  ................

         |- hash --||--------|  |------- alg ---------|
   0x0010 dc a7 2c a3 07 08 08 19  00 00 00 00 00 00 00 00  ..,.............

         |------- gcdata ------| |-- str --||ptrToThis|
   0x0020 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................

         |------- pkgPath -----| |------- name --------|
   0x0030 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................

         |------- typ ---------| |---- offsetAnon -----|
   0x0040 02 00 00 00 00 00 00 00  02 00 00 00 00 00 00 00  ................

         |------- name --------| |------- typ ---------|
   0x0050 00 00 00 00 00 00 00 00  40 00 00 00 00 00 00 00  ........@.......

         |---- offsetAnon -----|
   0x0060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
   0x0070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
   0x0080 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00  ................

   rel 24+8 t=1 type..alg."".MyStruct+0;定义了使用的<hash,equal>函数对
   rel 32+8 t=1 runtime.gcbits.02+0;gc使用
   rel 40+4 t=5 type..namedata.*main.MyStruct.+0;结构体名称
   rel 44+4 t=5 type.*"".MyStruct+0;*MyStruct类型
   rel 56+8 t=1 type."".MyStruct+96;
   rel 80+4 t=5 type..importpath."".+0
   rel 96+8 t=1 type..namedata.Id.+0
   rel 104+8 t=1 type.int64+0
   rel 120+8 t=1 type..namedata.Name.+0
   rel 128+8 t=1 type.string+0
   
type..namedata.*main.MyStruct. SRODATA dupok size=17
   0x0000 01 00 0e 2a 6d 61 69 6e 2e 4d 79 53 74 72 75 63  ...*main.MyStruc
   0x0010 74  

type..namedata.Id. SRODATA dupok size=5
   0x0000 01 00 02 49 64                                   ...Id

type..namedata.Name. SRODATA dupok size=7
   0x0000 01 00 04 4e 61 6d 65                             ...Name

3.3.3 myInterface

go.itab.*"".MyStruct,"".myInterface SRODATA dupok size=40

          |--------- inter -----|  |--------- _type -----|
   0x0000  00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00  ................

         |- hash --| |-- 对齐 --| | fun[0]  | | fun[1]  |
   0x0010 58 6c 00 8b  00 00 00 00   00 00 00 00  00 00 00 00  Xl..............

   0x0020 00 00 00 00 00 00 00 00                          ........

   rel 0+8 t=1 type."".myInterface+0
   rel 8+8 t=1 type.*"".MyStruct+0
   rel 24+8 t=1 "".(*MyStruct).test1+0
   rel 32+8 t=1 "".(*MyStruct).test2+0

3.3.4 类型转换

runtime/iface.go中给出了许多进行类型转换的接口,go编译器只在必要时(如强制类型转换)才调用下面接口

  • convT2E
  • convT2I
  • convT16
  • convTstring
  • convTslice
  • ...

4.go反射

以空接口为桥梁

4.1 TypeValue

4.1.1 TypertypeemptyInterface

1)Type

是一个接口,提供了许多用于访问类型元信息的方法

type Type interface {
        // 此类型的变量对齐后所占用的字节数
        Align() int
        
        // 如果是 struct 的字段,对齐后占用的字节数
        FieldAlign() int
        
        // 返回类型方法集里的第 `i` (传入的参数)个方法
        Method(int) Method
        
        // 通过名称获取方法
        MethodByName(string) (Method, bool)
        
        // 获取类型方法集里导出的方法个数
        NumMethod() int
        
        // 类型名称
        Name() string
        
        // 返回类型所在的路径,如:encoding/base64
        PkgPath() string
        
        // 返回类型的大小,和 unsafe.Sizeof 功能类似
        Size() uintptr
        
        // 返回类型的字符串表示形式
        String() string
        
        // 返回类型的类型值
        Kind() Kind
        
        // 类型是否实现了接口 u
        Implements(u Type) bool
        
        // 是否可以赋值给 u
        AssignableTo(u Type) bool
        
        // 是否可以类型转换成 u
        ConvertibleTo(u Type) bool
        
        // 类型是否可以比较
        Comparable() bool
        
        // 下面这些函数只有特定类型可以调用
        // 如:Key, Elem 两个方法就只能是 Map 类型才能调用
        // 类型所占据的位数
        Bits() int
        
        // 返回通道的方向,只能是 chan 类型调用
        ChanDir() ChanDir
        
        // 返回类型是否是可变参数,只能是 func 类型调用
        // 比如 t 是类型 func(x int, y ... float64)
        // 那么 t.IsVariadic() == true
        IsVariadic() bool
        
        // 返回内部子元素类型,只能由类型 Array, Chan, Map, Ptr, or Slice 调用
        Elem() Type
        
        // 返回结构体类型的第 i 个字段,只能是结构体类型调用
        // 如果 i 超过了总字段数,就会 panic
        Field(i int) StructField
        
        // 返回嵌套的结构体的字段
        FieldByIndex(index []int) StructField
        
        // 通过字段名称获取字段
        FieldByName(name string) (StructField, bool)
        
        // 传入一个含有判断逻辑的函数,返回名称符合函数内判断逻辑的字段
        FieldByNameFunc(match func(string) bool) (StructField, bool)
        
        // 获取函数类型的第 i 个参数的类型
        In(i int) Type
        
        // 返回 map 的 key 类型,只能由类型 map 调用
        Key() Type
        
        // 返回 Array 的长度,只能由类型 Array 调用
        Len() int
        
        // 返回类型字段的数量,只能由类型 Struct 调用
        NumField() int
        
        // 返回函数类型的输入参数个数
        NumIn() int
        
        // 返回函数类型的返回值个数
        NumOut() int
        
        // 返回函数类型的第 i 个值的类型
        Out(i int) Type
        
        // 返回类型结构体的相同部分
        common() *rtype
        
        // 返回类型结构体的不同部分
        uncommon() *uncommonType
}
2)rtype

结构类,是Type的实现,其元素和runtime下的_type完全一致,目的是为了反射期间对运行时类型变量进行成功转型

3)emptyInterface
type emptyInterface struct {
   typ  *rtype
   word unsafe.Pointer
}

此接口和runtime下的eface完全一致,目的是为了反射期间可以将eface成功转型

4)TypeOf函数

这里会用到unsafe.Pointer来完成成功的指针转型操作,零拷贝

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
}

调用此方法经过下面步骤

  • 转型为eface
  • 转型为emptyInterface
  • 取出实际类型rtype

得到rtype后,后续就可以通过调用Type提供的方法来反射获取类型的元信息

5)反射期和运行期的类型对比
反射(reflect包下) 运行时(runtime包下)
rtype _type
emptyInterface eface
xxxType xxxtype

xxx包括map、chan、interface、uncommon、array、func、ptr、slice、struct

4.1.2 Value

1)Value
type Value struct {
   //类型
   typ *rtype
   //值
   ptr unsafe.Pointer
   //标志位,不同位代表不同标志,包含是否可寻址、变量是否可导出等等
   flag
}

Value结构体很简单,由类型、原变量指针和标志位组成,其定义了许多方法,如

  • 直接修改原变量值的方法
 // 设置切片的 len 字段,如果类型不是切片,就会panic
 func (v Value) SetLen(n int)
 
 // 设置切片的 cap 字段
 func (v Value) SetCap(n int)
 
 // 设置字典的 kv
 func (v Value) SetMapIndex(key, val Value)

 // 返回切片、字符串、数组的索引 i 处的值
 func (v Value) Index(i int) Value

 // 根据名称获取结构体的内部字段值
 func (v Value) FieldByName(name string) Value
  • 判断可访问性的方法(围绕flag展开)
//可寻址
func (v Value) CanAddr() bool {
   return v.flag&flagAddr != 0
}

//可赋值
func (v Value) CanSet() bool {
   return v.flag&(flagAddr|flagRO) == flagAddr
}
  • .......
2)Header

“Go 语言里的引用类型有如下几个:切片、映射、通道、接口和函数类型。当声明上述类型的变量时,创建的变量被称作标头(header)值。从技术细节上说,字符串也是一种引用类型

每个引用类型创建的标头值是包含一个指向底层数据结构的指针。每个引用类型还包含一组独特的字段,用于管理底层数据结构。因为标头值是为复制而设计的,所以永远不需要共享一个引用类型的值。”

——《go语言实战》

go语言中提供了StringHeader和SliceHeader来反射修改属性,其余引用类型使用Value内置的方法进行操作

3)ValueOf方法
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}
}

经历如下步骤

  • 将变量转型为eface
  • 将eface转型为emptyInterface
  • 将实际类型、原变量指针(如果传入的是指针,如果传入的变量,获取到的是副本的指针),flag三者作为参数构建Value

4.2 常见误区

4.2.1 接口为nil

var buf *bytes.Buffer
//buf = new(bytes.Buffer) //赋值
var out io.Writer = buf
if out != nil {
   out.Write([]byte("done!
")) //buf赋值删掉时panic,因为type不为nil,value为nil
}

4.2.2 value不可修改原值

1)传入变量
func main(){
   //指针直接构成eface
   //变量需要调用convT2E构造eface
   var myStruct = MyStruct{
      Id:   1,
      Name: "sj",
   }
   val := reflect.ValueOf(myInter)
   fmt.Println(val)
}

在myStruct传入的值为

  • MyStruct变量

会触发runtime.convT2E

func convT2E(t *_type, elem unsafe.Pointer) (e eface) {
   if raceenabled {
      raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2E))
   }
   if msanenabled {
      msanread(elem, t.size)
   }
   x := mallocgc(t.size, t, true)typedmemmove(t, x, elem)
   e._type = t
   e.data = x
   return
}

这里可以看出,最终返回的eface中的data是堆上的副本的指针,不是原变量的指针

  • MyStruct变量指针

直接将传入的指针作为eface的data域,没有触发拷贝

从上可知,在调用ValueOf方法获取Value对象时,如果没有传入指针,则不可以正确的修改到原变量的值,因此go的CanAddr方法在这种情况下也会返回false

2)元素未导出

利用反射机制,对于结构体中未导出成员,可以读取,但不能修改其值。这种情况下CanSet方法返回false

4.3 示例代码

此示例包括了接口的构造过程、类型转换以及反射调用点

func main(){
   var myInter myInterface
   myInter = &MyStruct{
      Id:   1,
      Name: "sj",
   }
   reflect.ValueOf(myInter)
}
"".main STEXT size=114 args=0x0 locals=0x30
   0x0000 00000 (test1.go:26) TEXT   "".main(SB), ABIInternal, $48-0
   //判断是否要扩展栈
   0x0000 00000 (test1.go:26) MOVQ   (TLS), CX
   0x0009 00009 (test1.go:26) CMPQ   SP, 16(CX)
   0x000d 00013 (test1.go:26) JLS    107
   //移动SP指针,分配出48字节的栈空间
   0x000f 00015 (test1.go:26) SUBQ   $48, SP
   //存储调用点到栈底8字节
   0x0013 00019 (test1.go:26) MOVQ   BP, 40(SP)
   //BP中存储栈底地址
   0x0018 00024 (test1.go:26) LEAQ   40(SP), BP
   //一些初始化动作
   0x001d 00029 (test1.go:26) FUNCDATA   $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
   0x001d 00029 (test1.go:26) FUNCDATA   $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
   0x001d 00029 (test1.go:26) FUNCDATA   $2, gclocals·bfec7e55b3f043d1941c093912808913(SB)
   0x001d 00029 (test1.go:30) PCDATA $0, $1
   0x001d 00029 (test1.go:30) PCDATA $1, $0
   //将type."".MyStruct的指针传入AX
   0x001d 00029 (test1.go:30) LEAQ   type."".MyStruct(SB), AX
   0x0024 00036 (test1.go:30) PCDATA $0, $0
   //将type."".MyStruct的指针移入栈顶
   0x0024 00036 (test1.go:30) MOVQ   AX, (SP)
   //传入栈顶的type."".MyStruct的指针,调用newobject,其会在堆上申请一块大小相当的内存,
   //并返回一个*MyStruct,放入SP的8~15字节
   0x0028 00040 (test1.go:30) CALL   runtime.newobject(SB)
   0x002d 00045 (test1.go:30) PCDATA $0, $1
   //将*MyStruct传入AX
   0x002d 00045 (test1.go:30) MOVQ   8(SP), AX
   //将1赋给*MyStruct指向的堆中的Id字段
   0x0032 00050 (test1.go:29) MOVQ   $1, (AX)
   //将2赋给*MyStruct指向的堆中的StringHeader的Len
   0x0039 00057 (test1.go:30) MOVQ   $2, 16(AX)
   0x0041 00065 (test1.go:30) PCDATA $0, $2
   //将go.string."sj"的地址传入CX
   0x0041 00065 (test1.go:30) LEAQ   go.string."sj"(SB), CX
   0x0048 00072 (test1.go:30) PCDATA $0, $1
   //将&"sj"赋给*MyStruct指向的堆中的StringHeader的Data
   0x0048 00072 (test1.go:30) MOVQ   CX, 8(AX)
   
//--------------至此,MyStruct对象构造完成------------------//

   0x004c 00076 (test1.go:32) PCDATA $0, $2
   //将go.itab.*"".MyStruct,"".myInterface+8也即type.*"".MyStruct的地址传入CX
   //这里是经过编译器优化的,如果开启-N禁止优化,此处指令应为
   //LEAQ    go.itab.*"".MyStruct,"".myInterface(SB), CX
   0x004c 00076 (test1.go:32) MOVQ   go.itab.*"".MyStruct,"".myInterface+8(SB), CX
   0x0053 00083 (test1.go:32) PCDATA $0, $1
   //将type.*"".MyStruct的地址移到栈顶
   //如果开启-N禁止优化,此处传入*go.itab.*"".MyStruct,"".myInterface
   0x0053 00083 (test1.go:32) MOVQ   CX, (SP) 
   
   /*
     若没有ValueOf调用,则栈顶会传入*go.itab.*"".MyStruct,"".myInterface(无关-N),
     至此就完成了类型转换,但是,这里调用了ValueOf,其需要转换为eface,所以需要传入
     *type.*"".MyStruct,所以开启了编译器优化后会直接传入此地址而不是先传itab再后移8字节传入		   
     *type.*"".MyStruct
   */

   0x0057 00087 (test1.go:32) PCDATA $0, $0
   //将*MyStruct移到SP的8~15字节
   0x0057 00087 (test1.go:32) MOVQ   AX, 8(SP) 
   //传入type.*"".MyStruct的地址调用ValueOf
   //如果将指针改为变量,则会触发convT2E
   //如果禁用编译器优化,栈顶传入的仍为type.*"".MyStruct
   0x005c 00092 (test1.go:32) CALL   reflect.ValueOf(SB)
   /*
   func ValueOf(i interface{}) Value {
       if i == nil {
          return Value{}
       }
       return unpackEface(i)
    }
   **/

   //刷新BP,存储栈底地址
   0x0061 00097 (test1.go:33) MOVQ   40(SP), BP
   //移动SP指针只鸢尾,栈的内存被回收
   0x0066 00102 (test1.go:33) ADDQ   $48, SP
   0x006a 00106 (test1.go:33) RET
   0x006b 00107 (test1.go:33) NOP
   0x006b 00107 (test1.go:26) PCDATA $1, $-1
   0x006b 00107 (test1.go:26) PCDATA $0, $-1
   //扩展栈
   0x006b 00107 (test1.go:26) CALL   runtime.morestack_noctxt(SB)
   0x0070 00112 (test1.go:26) JMP    0
   0x0000 65 48 8b 0c 25 00 00 00 00 48 3b 61 10 76 5c 48  eH..%....H;a.vH
   0x0010 83 ec 30 48 89 6c 24 28 48 8d 6c 24 28 48 8d 05  ..0H.l$(H.l$(H..
   0x0020 00 00 00 00 48 89 04 24 e8 00 00 00 00 48 8b 44  ....H..$.....H.D
   0x0030 24 08 48 c7 00 01 00 00 00 48 c7 40 10 02 00 00  $.H......H.@....
   0x0040 00 48 8d 0d 00 00 00 00 48 89 48 08 48 8b 0d 00  .H......H.H.H...
   0x0050 00 00 00 48 89 0c 24 48 89 44 24 08 e8 00 00 00  ...H..$H.D$.....
   0x0060 00 48 8b 6c 24 28 48 83 c4 30 c3 e8 00 00 00 00  .H.l$(H..0......
   0x0070 eb 8e                                            ..

   rel 5+4 t=16 TLS+0
   rel 32+4 t=15 type."".MyStruct+0
   rel 41+4 t=8 runtime.newobject+0
   rel 68+4 t=15 go.string."sj"+0
   rel 79+4 t=15 go.itab.*"".MyStruct,"".myInterface+8
   rel 93+4 t=8 reflect.ValueOf+0
   rel 108+4 t=8 runtime.morestack_noctxt+0

内存分布如下所示

img

5.总结

通过研究go的接口和反射,发现下面两条原则

  • 想要实现反射需要有入口获取元信息,接口就是这个入口,元信息在编译后就已生成
  • 指针和变量触发的拷贝决定了是否是对同一个对象进行的操作

#附

#1.引用

  1. A Quick Guide to Go"s Assembler
  2. 从栈上理解 Go语言函数调用 - luozhiyun - 云海天 (cnblogs.com)
  3. [译]Go 和 interface 探究 (xargin.com)
  4. 深入研究 Go interface 底层实现 (halfrost.com)
  5. Go 语言接口的原理 | Go 语言设计与实现 (draveness.me)
  6. 深度解密Go语言之关于 interface 的 10 个问题
  7. Go 语言反射的实现原理 | Go 语言设计与实现 (draveness.me)
  8. 深度解密Go语言之反射 | qcrao
  9. 有点不安全却又一亮的 Go unsafe.Pointer - SegmentFault 思否
  10. 《Go语言实战》

#2.触发类型转换的例子

func main(){
   var myInter myInterface
   myInter = &MyStruct{
      Id:   1,
      Name: "sj",
   }

   //convI2I
   var subInterface subInterface
   subInterface = myInter
   fmt.Println(subInterface)

   //convT2E
   var emptyInterface interface{}
   val := reflect.ValueOf(myInter)
   emptyInterface = val
   fmt.Println(emptyInterface)
}
//convI2I

0x0050 00080 (test1.go:36)  LEAQ   type."".subInterface(SB), CX

0x0057 00087 (test1.go:36) PCDATA $0, $1

0x0057 00087 (test1.go:36) MOVQ   CX, (SP)

0x005b 00091 (test1.go:36) PCDATA $0, $2

0x005b 00091 (test1.go:36) LEAQ   go.itab.*"".MyStruct,"".myInterface(SB), CX

0x0062 00098 (test1.go:36) PCDATA $0, $1

0x0062 00098 (test1.go:36) MOVQ   CX, 8(SP)

0x0067 00103 (test1.go:36) PCDATA $0, $0

0x0067 00103 (test1.go:36) MOVQ   AX, 16(SP)

//这里显式调用了convI2I将iface类型转换为iface类型

0x006c 00108 (test1.go:36) CALL   runtime.convI2I(SB)

/*
    //未触发堆上内存分配,直接将itab和data进行复制

    func convI2I(inter *interfacetype, i iface) (r iface) {
       tab := i.tab
       if tab == nil {
          return
       }

       if tab.inter == inter {
          r.tab = tab
          r.data = i.data
          return
       }
       //根据两个接口间的方法集合判断两个集合的交集是否等于目标接口的集合(方法签名)
       r.tab = getitab(inter, tab._type, false)
       r.data = i.data
       return
    }

**/



//convT2E

0x020f 00527 (test1.go:40)  LEAQ   type.reflect.Value(SB), AX

0x0216 00534 (test1.go:40) PCDATA $0, $0

0x0216 00534 (test1.go:40) MOVQ   AX, (SP)

0x021a 00538 (test1.go:40) PCDATA $0, $1

0x021a 00538 (test1.go:40) PCDATA $1, $0

0x021a 00538 (test1.go:40) LEAQ   ""..autotmp_5+216(SP), AX

0x0222 00546 (test1.go:40) PCDATA $0, $0

0x0222 00546 (test1.go:40) MOVQ   AX, 8(SP)

0x0227 00551 (test1.go:40) CALL   runtime.convT2E(SB)

/*
    //触发了在堆上申请内存并拷贝的动作
    func convT2E(t *_type, elem unsafe.Pointer) (e eface) {
       if raceenabled {
          raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2E))
       }

       if msanenabled {
          msanread(elem, t.size)
       }
       x := mallocgc(t.size, t, true)
       typedmemmove(t, x, elem)
       e._type = t
       e.data = x
       return
    }
**/

#3.interface、Type、Value三者转换

img