Go-接口interface底层实现

2023-02-27 09:32:00 浏览数 (2)

前言

Go语言中的接口类型会根据是否包含一组方法而分成两种不同的实现,分别为包含一组方法的iface结构体和不包含任何方法的eface结构体。我们将从这两个结构的底层数据结构说起,然后在interface编译时具体类型赋值给接口时是如果进行转换的。

iface和eface的底层数据结构在src/runtime/runtime2.go文件中

1:eface

我们先来看eface的结构,相对于iface它的结构比较简单

代码语言:javascript复制
type eface struct {
 _type *_type     // 空接口具体的实现类型
 data  unsafe.Pointer // 具体的值
}

data字段是eface和iface都有的,是一个内存指针,指向接口数据的存储地址,再看_type,它实际是在src/runtime/type.go中,

代码语言:javascript复制
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    //基础类型枚举值,有26个基础类型
 // function for comparing objects of this type
 // (ptr to object A, ptr to object B) -> ==?
 equal func(unsafe.Pointer, unsafe.Pointer) bool  // 比较两个形参对应对象的类型是否相等
 gcdata    *byte
 str       nameOff
 ptrToThis typeOff
}

我们知道Go 语言是强类型语言,编译时对每个变量的类型信息做强校验,所以每个类型的元信息要用一个结构体描述。再者 Go 的反射也是基于类型的元信息实现的。_type 就是所有类型最原始的元信息。像类型名称,大小,对齐边界,是否为自定义类型等信息,是每个类型元数据都要记录的。所以被放到了runtime._type结构体中。

编辑切换为居中

eface数据结构

2:iface

再看iface,与eface不同的是iface结构体中要同时存储方法信息,它的结构如下:

代码语言:javascript复制
type iface struct {
 tab  *itab
 data unsafe.Pointer
}

tab 中存放的是类型、方法等信息,data 指针指向的 iface 绑定对象的原始数据

代码语言:javascript复制
type itab struct {
 inter *interfacetype    // 接口自身定义的类型信息,用于定位到具体interface类型
 _type *_type   // 接口实际指向值的类型信息-实际对象类型
 hash  uint32    // itab.hash是从itab._type中拷贝来的,是类型的哈希值,用于快速判断类型是否相等时使用
 _     [4]byte
 //variable sized. fun[0]==0 means _type does not implement inter
 fun   [1]uintptr   
 // 动态数组,接口方法实现列表(方法集),即函数地址列表
}

itab的_type就是iface的动态类型,就是赋值给接口类型的那个变量的数据类型,跟eface指向的是同一个结构。

itab的inter是interface的类型元数据,它里面记录了这个接口类型的描述信息,接口要求的方法列表就记录在interfacetype.mhdr这里。

itb的fun当fun0为0时,说明_type并没有实现该接口,当有实现接口时,fun存放了第一个接口方法的地址,其他方法一次往下存放,这里就简单用空间换时间,其实方法都在_type字段中能找到,实际在这记录下,每次调用的时候就不用动态查找了。

代码语言:javascript复制
type interfacetype struct {
 typ     _type
 pkgpath name
 mhdr    []imethod
}

编辑切换为居中

iface数据结构

3:举例代码

代码语言:javascript复制
type Eater interface {
 Eat(foodName string)
}

type Dog struct {
}

func (d Dog) Eat(food string) {
 fmt.Println("dog eat", food)
}

//判断结构体Dog是否实现了Eater接口
//保证接口都被实现,否则在编译时就会报错(推荐使用)
var _ Eater = Dog{}

func main() {
 var eat Eater = Dog{}
 eat.Eat("meat")
}

//结果打印:dog eat meat

4:编译时如何转换

interface的底层数据结构我们已经知道了,但是为什么底层结构在runtime中,究竟在编译的时候是怎么转换的呢?

我们在分析Go底层的时候往往会通过汇编来看,对我而言对汇编不太清楚的,不过通过查阅资料了解了一些interface在编译期间怎么进行转换的,有一句话对Go编译描述的很巧妙。

几乎没有任何一个 Go 汇编底层问题不是用一条 go tool compile 不能解决的,如果不行的话,就用 go tool objdump,总能知道是怎么回事

代码语言:javascript复制
go tool compile -S main.go  // 反编译代码为汇编代码

go tool objdump // 可用于查看任意函数的机器码、汇编指令、偏移

Go程序在编译的时候会生成汇编,汇编器会将汇编代码转变成机器可以执行的指令,每一条汇编语句几乎都与一条机器指令相对应。

代码语言:javascript复制
//上面的举例代码经过go tool compile -S 之后有这么一个runtime的调用函数
 CALL  runtime.convT2I(SB)  //  具体类型赋值给接口类型是,这里调用的是convT2I()

// Type to non-empty-interface conversion.
func convT2I(tab *byte, elem *any) (ret any)

//具体类型赋值给空接口
// Type to empty-interface conversion.
func convT2E(typ *byte, elem *any) (ret any)

4.1:convT2I

convT2I 函数的实现在src/runtime/iface.go,根据tab._type的类型的大小t.size 使用mallocgc申请一块内存空间,然后将elem指针的内容拷贝到申请的空间x,然后对iface的tab和data进行赋值,这样就完成了iface的创建。

代码语言:javascript复制
func convT2I(tab *itab, elem unsafe.Pointer) (i iface) {
 t := tab._type
 if raceenabled {
  raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2I))
 }
 if msanenabled {
  msanread(elem, t.size)
 }
 x := mallocgc(t.size, t, true)
 typedmemmove(t, x, elem)
 i.tab = tab
 i.data = x
 return
}

4.2:convT2E

convT2E 和 convT2I类似,同样在转换成eface时*_type是由编译器生成,当做入参调用convT2E

代码语言:javascript复制
// 空接口转换函数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)
  // TODO: We allocate a zeroed object only to overwrite it with actual data.
  // Figure out how to avoid zeroing. Also below in convT2Eslice, convT2I, convT2Islice.
  typedmemmove(t, x, elem)
  e._type = t
  e.data = x
  return
}

0 人点赞