加入收藏 | 设为首页 | 会员中心 | 我要投稿 应用网_丽江站长网 (http://www.0888zz.com/)- 科技、建站、数据工具、云上网络、机器学习!
当前位置: 首页 > 综合聚焦 > 编程要点 > 语言 > 正文

Go内存中的接口种类

发布时间:2021-11-04 04:32:30 所属栏目:语言 来源:互联网
导读:前言抽象来讲,接口,是一种约定,是一种约束,是一种协议。在Go语言中,接口是一种语法类型,用来定义一种编程规范。在Go语言中,接口主要有两类:没有方法定义的空接口有方法定义的非空接口之前,有两篇图文详细介绍了空接口对象及其类型:【Go】内存中的
前言 抽象来讲,接口,是一种约定,是一种约束,是一种协议。   在Go语言中,接口是一种语法类型,用来定义一种编程规范。   在Go语言中,接口主要有两类:   没有方法定义的空接口   有方法定义的非空接口   之前,有两篇图文详细介绍了空接口对象及其类型:   【Go】内存中的空接口 【Go】再谈空接口 本文将深入探究包含方法的非空接口,以下简称接口。   环境 OS : Ubuntu 20.04.2 LTS; x86_64  Go : go version go1.16.2 linux/amd64  声明 操作系统、处理器架构、Go版本不同,均有可能造成相同的源码编译后运行时的寄存器值、内存地址、数据结构等存在差异。   本文仅包含 64 位系统架构下的 64 位可执行程序的研究分析。   本文仅保证学习过程中的分析数据在当前环境下的准确有效性。   代码清单 // interface_in_memory.go  package main    import "fmt"  import "reflect"  import "strconv"    type foo interface {    fmt.Stringer    Foo()    ree()  }    type fooImpl int    //go:noinline  func (i fooImpl) Foo() {    println("hello foo")  }    //go:noinline  func (i fooImpl) ree() {    println("hello ree")  }    //go:noinline  func (i fooImpl) String() string {    return strconv.Itoa(int(i))  }    func main() {    impl := fooImpl(123)    impl.Foo()    impl.ree()    fmt.Println(impl.String())    typeOf(impl)    exec(impl)  }    //go:noinline  func exec(foo foo) {    foo.Foo()    foo.ree()    fmt.Println(foo.String())    typeOf(foo)    fmt.Printf("exec 参数类型地址:%pn", reflect.TypeOf(exec).In(0))  }    //go:noinline  func typeOf(i interface{}) {    v := reflect.ValueOf(i)    t := v.Type()    fmt.Printf("类型:%sn", t.String())    fmt.Printf("地址:%pn", t)    fmt.Printf("值  :%dn", v.Int())    fmt.Println()  }  以上代码,定义了一个包含3个方法的接口类型foo,还定义了一个fooImpl类型。在语法上,我们称fooImpl类型实现了foo接口。   运行结果     程序结构     数据结构介绍 接口数据类型的结构定义在reflect/type.go源文件中,如下所示:   // 表示一个接口方法  type imethod struct {    name nameOff // 方法名称相对程序 .rodata 节的偏移量    typ  typeOff // 方法类型相对程序 .rodata 节的偏移量  }    // 表示一个接口数据类型  type interfaceType struct {    rtype             // 基础信息    pkgPath name      // 包路径信息    methods []imethod // 接口方法  }  其实这只是一个表象,完整的接口数据类型结构如下伪代码所示:   // 表示一个接口类型  type interfaceType struct {    rtype             // 基础信息    pkgPath name      // 包路径信息    methods []imethod // 接口方法的 slice,实际指向 array 字段    u uncommonType    // 占位    array [len(methods)]imethod // 实际的接口方法数据  }  完整的结构分布图如下:       另外两个需要了解的结构体,之前文章已经多次介绍过,也在reflect/type.go源文件中,定义如下:   type uncommonType struct {      pkgPath nameOff  // 包路径名称偏移量      mcount  uint16   // 方法的数量      xcount  uint16   // 公共导出方法的数量      moff    uint32   // [mcount]method 相对本对象起始地址的偏移量      _       uint32   // unused  }  reflect.uncommonType结构体用于描述一个数据类型的包名和方法信息。对于接口类型,意义不是很大。   // 非接口类型的方法  type method struct {      name nameOff // 方法名称偏移量      mtyp typeOff // 方法类型偏移量      ifn  textOff // 通过接口调用时的地址偏移量;接口类型本文不介绍      tfn  textOff // 直接类型调用时的地址偏移量  }  reflect.method结构体用于描述一个非接口类型的方法,它是一个压缩格式的结构,每个字段的值都是一个相对偏移量。   type nameOff int32 // offset to a name  type typeOff int32 // offset to an *rtype  type textOff int32 // offset from top of text section  nameOff 是相对程序 .rodata 节起始地址的偏移量。 typeOff 是相对程序 .rodata 节起始地址的偏移量。 textOff 是相对程序 .text 节起始地址的偏移量。 接口实现类型 从以上“运行结果”可以看到,fooImpl的类型信息位于0x4a9be0内存地址处。   关于fooImpl类型,【Go】再谈整数类型一文曾进行过非常详细的介绍,此处仅分析其方法相关内容。   查看fooImpl类型的内存数据如下:       绘制成图表如下:       fooImpl类型有3个方法,我们以Foo方法来说明接口相关的底层原理。   Foo方法的相关数据如下:   var Foo = reflect.method {    name: 0x00000172, // 方法名称相对程序 `.rodata` 节起始地址的偏移量    mtyp: 0x00009960, // 方法类型相对程序 `.rodata` 节起始地址的偏移量    ifn:  0x000989a0, // 接口调用的指令相对程序 `.text` 节起始地址的偏移量    tfn:  0x00098160, // 正常调用的指令相对程序 `.text` 节起始地址的偏移量  }  方法名称 method.name用于定位方法的名称,即一个reflect.name对象。   Foo方法的reflect.name对象位于 0x49a172(0x00000172 + 0x49a000)地址处,毫无疑问,解析结果是Foo。   (gdb) p /x 0x00000172 + 0x49a000  $3 = 0x49a172  (gdb) x /3bd 0x49a172  0x49a172:  1  0  3  (gdb) x /3c 0x49a172 + 3  0x49a175:  70 'F'  111 'o'  111 'o'  (gdb)  方法类型 method.mtyp用于定位方法的数据类型,即一个reflect.funcType对象。   Foo方法的reflect.funcType对象,其位于0x4a3960(0x00009960 + 0x49a000)地址处。   Foo方法的数据类型的字符串表示形式是func()。   (gdb) x /56bx 0x4a3960  0x4a3960:  0x08  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x4a3968:  0x08  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x4a3970:  0xf6  0xbc  0x82  0xf6  0x02  0x08  0x08  0x33  0x4a3978:  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x4a3980:  0xa0  0x4a  0x4c  0x00  0x00  0x00  0x00  0x00  0x4a3988:  0x34  0x11  0x00  0x00  0x00  0x00  0x00  0x00  0x4a3990:  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  (gdb) x /wx 0x4a3988  0x4a3988:  0x00001134  (gdb) x /s 0x00001134 + 0x49a000 + 3  0x49b137:  "*func()"  (gdb)  想要深入了解函数类型,请阅读【Go】内存中的函数。   接口方法 method.ifn字段的英文注释为function used in interface call,即调用接口方法时使用的函数。   在本例中,就是通过foo接口调用fooImpl类型的Foo函数时需要执行的指令集合。   具体来讲就是,代码清单中的exec函数内调用Foo方法需要执行的指令集合。   Foo函数的method.ifn = 0x000989a0,计算出其指令集合位于地址0x4999a0(0x000989a0 + 0x401000)处。       通过内存数据可以清楚地看到,接口方法的符号是main.(*fooImpl).Foo。该函数主要做了两件事:   检查panic   在0x4999d7地址处调用另一个函数main.fooImpl.Foo。   类型方法 method.tfn字段的英文注释为function used for normal method call,即正常方法调用时使用的函数。   在本例中,就是通过fooImpl类型的对象调用Foo函数时需要执行的指令集合。   具体来讲就是,代码清单中的main函数内调用Foo方法需要执行的指令集合。   Foo函数的method.tfn = 0x00098160,计算出其指令集合位于地址0x499160(0x00098160 + 0x401000)处。

(编辑:应用网_丽江站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读