转自 https://zhuanlan.zhihu.com/p/27652856
先看一段代码:
上面的例子的输出结果如下
可能你会感觉奇怪,为什么会是 non-empty inerface,那么继续往下看,你就会知道答案。
interface 底层结构
根据 interface 是否包含有 method,底层实现上用两种 struct 来表示:iface 和 eface。eface表示不含 method 的 interface 结构,或者叫 empty interface。对于 Golang 中的大部分数据类型都可以抽象出来 _type 结构,同时针对不同的类型还会有一些其他信息。
iface 表示 non-empty interface 的底层实现。相比于 empty interface,non-empty 要包含一些 method。method 的具体实现存放在 itab.fun 变量里。如果 interface 包含多个 method,这里只有一个 fun 变量怎么存呢?这个下面再细说。
我们使用实际程序来看一下。
查看汇编代码。
代码 17 行 var y interface{} = x 调用了函数 runtime.convT2E,将 int 类型的 x 转换成 empty interface。代码 19 行 var t MyInterface = s 将 MyStruct 类型转换成 non-empty interface: MyInterface。
看上面的函数原型,可以看出中间过程编译器将根据我们的转换目标类型的 empty interface 还是 non-empty interface,来对原数据类型进行转换(转换成 <_type, unsafe.Pointer> 或者 <itab, unsafe.Pointer>)。这里对于 struct 满不满足 interface 的类型要求(也就是 struct 是否实现了 interface 的所有 method),是由编译器来检测的。
itab
iface 结构中最重要的是 itab 结构。itab 可以理解为 pair 。当然 itab 里面还包含一些其他信息,比如 interface 里面包含的 method 的具体实现。下面细说。itab 的结构如下。
其中 interfacetype 包含了一些关于 interface 本身的信息,比如 package path,包含的 method。上面提到的 iface 和 eface 是数据类型(built-in 和 type-define)转换成 interface 之后的实体的 struc 大专栏 Golang Interface 解析t 结构,而这里的 interfacetype 是我们定义 interface 时候的一种抽象表示。
_type 表示 concrete type。fun 表示的 interface 里面的 method 的具体实现。比如 interface type 包含了 method A, B,则通过 fun 就可以找到这两个 method 的具体实现。这里有个问题 fun 是长度为 1 的 uintptr 数组,那么怎么表示多个 method 呢?看一下测试程序。
看一下函数调用对应的汇编代码。
其中 0x18(SP) 对应的 itab 的地址。fun 在 x86-64 机器上对应 itab 内的地址偏移为 8+8+8+4+4 = 32 = 0x20,也就是 0x20(AX) 对应的 fun 的值,此时存放的 AWK 函数地址。然后 0x28(AX) = &Hello,0x30(AX) = &Print,0x38(AX) = &World。对的,每次函数是按字典序排序存放的。
我们再来看一下函数地址究竟是怎么写入的?首先 Golang 中的 uintptr 一般用来存放指针的值,这里对应的就是函数指针的值(也就是函数的调用地址)。但是这里的 fun 是一个长度为 1 的 uintptr 数组。我们看一下 runtime 包的 additab 函数。
上面的代码的意思是在 fun[0] 的地址后面依次写入其他 method 对应的函数指针。熟悉 C++ 的同学可以类比 C++ 的虚函数表指针来看。
剩下的还有 bad,link,inhash。其中 bad 是一个表征 itab 状态的变量。而这里的 link 是 *itab 类型,是不是表示 interface 的嵌套呢?并不是,interface 的嵌套也是把 method 平铺而已。link 要和 inhash 一起来说。在 runtime 包里面有一个 hash 表,通过 hash[hashitab(interface_type, concrete_type)] 可以取得 itab,这是出于性能方面的考虑。主要代码如下,这里就不再赘述了。
3. Type Assertion
我们知道使用 interface type assertion (中文一般叫断言) 的时候需要注意,不然很容易引入 panic。
这个过程体现在下面的几个函数上。
4. 总结
从某种意义上来说,Golang 的 interface 也是一种多态的体现。对比其他支持多态特性的语言,实现还是略有差异,很难说谁好谁坏。