转自 https://zhuanlan.zhihu.com/p/27652856
先看一段代码:
1 2 3 4 5 6 7 8 9 10 11 12
func ( x interface {}) { if x == nil { fmt . Println ( "empty interface" ) return } fmt . Println ( "non-empty interface" ) } func main () { var x * int = nil Foo ( x ) }
上面的例子的输出结果如下
1 2
$ go run test_interface . go non - empty interface
可能你会感觉奇怪,为什么会是 non-empty inerface,那么继续往下看,你就会知道答案。
interface 底层结构
根据 interface 是否包含有 method,底层实现上用两种 struct 来表示:iface 和 eface。eface表示不含 method 的 interface 结构,或者叫 empty interface。对于 Golang 中的大部分数据类型都可以抽象出来 _type 结构,同时针对不同的类型还会有一些其他信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
type eface struct { _type * _type data unsafe . Pointer } type _type struct { size uintptr ptrdata uintptr // size of memory prefix holding all 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 }
iface 表示 non-empty interface 的底层实现。相比于 empty interface,non-empty 要包含一些 method。method 的具体实现存放在 itab.fun 变量里。如果 interface 包含多个 method,这里只有一个 fun 变量怎么存呢?这个下面再细说。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
type iface struct { tab * itab data unsafe . Pointer } // layout of Itab known to compilers // allocated in non-garbage-collected memory // Needs to be in sync with // ../cmd/compile/internal/gc/reflect.go:/^func.dumptypestructs. type itab struct { inter * interfacetype _type * _type link * itab bad int32 inhash int32 // has this itab been added to hash? fun [ 1 ] uintptr // variable sized }
我们使用实际程序来看一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
package main import ( "fmt" ) type MyInterface interface { Print () } type MyStruct struct {} func ( ms MyStruct ) Print () {} func main () { x := 1 var y interface {} = x var s MyStruct var t MyInterface = s fmt . Println ( y , z ) }
查看汇编代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
$ go build - gcflags '-l' - o interface11 interface11 . go $ go tool objdump - s "main.main" interface11 TEXT main . main ( SB ) / Users / kltao / code / go / examples / interface11 . go interface11 . go : 15 0x10870f0 65488b0c25a0080000 GS MOVQ GS : 0x8a0 , CX interface11 . go : 15 0x10870f9 483b6110 CMPQ 0x10 ( CX ), SP interface11 . go : 15 0x10870fd 0f86de000000 JBE 0x10871e1 interface11 . go : 15 0x1087103 4883ec70 SUBQ $0x70 , SP interface11 . go : 15 0x1087107 48896c2468 MOVQ BP , 0x68 ( SP ) interface11 . go : 15 0x108710c 488d6c2468 LEAQ 0x68 ( SP ), BP interface11 . go : 17 0x1087111 48c744243001000000 MOVQ $0x1 , 0x30 ( SP ) interface11 . go : 17 0x108711a 488d057fde0000 LEAQ 0xde7f ( IP ), AX interface11 . go : 17 0x1087121 48890424 MOVQ AX , 0 ( SP ) interface11 . go : 17 0x1087125 488d442430 LEAQ 0x30 ( SP ), AX interface11 . go : 17 0x108712a 4889442408 MOVQ AX , 0x8 ( SP ) interface11 . go : 17 0x108712f e87c45f8ff CALL runtime . convT2E ( SB ) interface11 . go : 17 0x1087134 488b442410 MOVQ 0x10 ( SP ), AX interface11 . go : 17 0x1087139 4889442438 MOVQ AX , 0x38 ( SP ) interface11 . go : 17 0x108713e 488b4c2418 MOVQ 0x18 ( SP ), CX interface11 . go : 17 0x1087143 48894c2440 MOVQ CX , 0x40 ( SP ) interface11 . go : 19 0x1087148 488d15b1000800 LEAQ 0x800b1 ( IP ), DX interface11 . go : 19 0x108714f 48891424 MOVQ DX , 0 ( SP ) interface11 . go : 19 0x1087153 488d542430 LEAQ 0x30 ( SP ), DX interface11 . go : 19 0x1087158 4889542408 MOVQ DX , 0x8 ( SP ) interface11 . go : 19 0x108715d e8fe45f8ff CALL runtime . convT2I ( SB )
代码 17 行 var y interface{} = x 调用了函数 runtime.convT2E,将 int 类型的 x 转换成 empty interface。代码 19 行 var t MyInterface = s 将 MyStruct 类型转换成 non-empty interface: MyInterface。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
func convT2E ( t * _type , elem unsafe . Pointer ) ( e eface ) { ... x := newobject ( t ) typedmemmove ( t , x , elem ) e . _type = t e . data = x return } func convT2I ( tab * itab , elem unsafe . Pointer ) ( i iface ) { t := tab . _type ... x := newobject ( t ) typedmemmove ( t , x , elem ) i . tab = tab i . data = x return }
看上面的函数原型,可以看出中间过程编译器将根据我们的转换目标类型的 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 的结构如下。
1 2 3 4 5 6 7 8
type itab struct { inter * interfacetype _type * _type link * itab bad int32 inhash int32 // has this itab been added to hash? fun [ 1 ] uintptr // variable sized }
其中 interfacetype 包含了一些关于 interface 本身的信息,比如 package path,包含的 method。上面提到的 iface 和 eface 是数据类型(built-in 和 type-define)转换成 interface 之后的实体的 struc 大专栏 Golang Interface 解析 t 结构,而这里的 interfacetype 是我们定义 interface 时候的一种抽象表示。
1 2 3 4 5 6 7 8 9 10
type interfacetype struct { typ _type pkgpath name mhdr [] imethod } type imethod struct { //这里的 method 只是一种函数声明的抽象,比如 func Print() error name nameOff ityp typeOff }
_type 表示 concrete type。fun 表示的 interface 里面的 method 的具体实现。比如 interface type 包含了 method A, B,则通过 fun 就可以找到这两个 method 的具体实现。这里有个问题 fun 是长度为 1 的 uintptr 数组,那么怎么表示多个 method 呢?看一下测试程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
package main type MyInterface interface { Print () Hello () World () AWK () } func Foo ( me MyInterface ) { me . Print () me . Hello () me . World () me . AWK () } type MyStruct struct {} func ( me MyStruct ) Print () {} func ( me MyStruct ) Hello () {} func ( me MyStruct ) World () {} func ( me MyStruct ) AWK () {} func main () { var me MyStruct Foo ( me ) }
看一下函数调用对应的汇编代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
$ go build - gcflags '-l' - o interface8 interface8 . go $ go tool objdump - s "main.Foo" interface8 TEXT main . Foo ( SB ) / Users / kltao / code / go / examples / interface8 . go interface8 . go : 10 0x104c060 65488b0c25a0080000 GS MOVQ GS : 0x8a0 , CX interface8 . go : 10 0x104c069 483b6110 CMPQ 0x10 ( CX ), SP interface8 . go : 10 0x104c06d 7668 JBE 0x104c0d7 interface8 . go : 10 0x104c06f 4883ec10 SUBQ $0x10 , SP interface8 . go : 10 0x104c073 48896c2408 MOVQ BP , 0x8 ( SP ) interface8 . go : 10 0x104c078 488d6c2408 LEAQ 0x8 ( SP ), BP interface8 . go : 11 0x104c07d 488b442418 MOVQ 0x18 ( SP ), AX interface8 . go : 11 0x104c082 488b4830 MOVQ 0x30 ( AX ), CX //取得 Print 函数地址 interface8 . go : 11 0x104c086 488b542420 MOVQ 0x20 ( SP ), DX interface8 . go : 11 0x104c08b 48891424 MOVQ DX , 0 ( SP ) interface8 . go : 11 0x104c08f ffd1 CALL CX // 调用 Print() interface8 . go : 12 0x104c091 488b442418 MOVQ 0x18 ( SP ), AX interface8 . go : 12 0x104c096 488b4828 MOVQ 0x28 ( AX ), CX //取得 Hello 函数地址 interface8 . go : 12 0x104c09a 488b542420 MOVQ 0x20 ( SP ), DX interface8 . go : 12 0x104c09f 48891424 MOVQ DX , 0 ( SP ) interface8 . go : 12 0x104c0a3 ffd1 CALL CX //调用 Hello() interface8 . go : 13 0x104c0a5 488b442418 MOVQ 0x18 ( SP ), AX interface8 . go : 13 0x104c0aa 488b4838 MOVQ 0x38 ( AX ), CX //取得 World 函数地址 interface8 . go : 13 0x104c0ae 488b542420 MOVQ 0x20 ( SP ), DX interface8 . go : 13 0x104c0b3 48891424 MOVQ DX , 0 ( SP ) interface8 . go : 13 0x104c0b7 ffd1 CALL CX //调用 World() interface8 . go : 14 0x104c0b9 488b442418 MOVQ 0x18 ( SP ), AX interface8 . go : 14 0x104c0be 488b4020 MOVQ 0x20 ( AX ), AX //取得 AWK 函数地址 interface8 . go : 14 0x104c0c2 488b4c2420 MOVQ 0x20 ( SP ), CX interface8 . go : 14 0x104c0c7 48890c24 MOVQ CX , 0 ( SP ) interface8 . go : 14 0x104c0cb ffd0 CALL AX //调用 AWK() interface8 . go : 15 0x104c0cd 488b6c2408 MOVQ 0x8 ( SP ), BP interface8 . go : 15 0x104c0d2 4883c410 ADDQ $0x10 , SP interface8 . go : 15 0x104c0d6 c3 RET interface8 . go : 10 0x104c0d7 e8f48bffff CALL runtime . morestack_noctxt ( SB ) interface8 . go : 10 0x104c0dc eb82 JMP main . Foo ( SB )
其中 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 函数。
1 2 3 4 5
func additab ( m * itab , locked , canfail bool ) { ... *(* unsafe . Pointer )( add ( unsafe . Pointer (& m . fun [ 0 ]), uintptr ( k )* sys . PtrSize )) = ifn ... }
上面的代码的意思是在 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,这是出于性能方面的考虑。主要代码如下,这里就不再赘述了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
const ( hashSize = 1009 ) var ( ifaceLock mutex // lock for accessing hash hash [ hashSize ]* itab ) func itabhash ( inter * interfacetype , typ * _type ) uint32 { // compiler has provided some good hash codes for us. h := inter . typ . hash h += 17 * typ . hash // TODO(rsc): h += 23 * x.mhash ? return h % hashSize } func additab (...) { ... h := itabhash ( inter , typ ) m . link = hash [ h ] m . inhash = 1 atomicstorep ( unsafe . Pointer (& hash [ h ]), unsafe . Pointer ( m )) }
3. Type Assertion
我们知道使用 interface type assertion (中文一般叫断言) 的时候需要注意,不然很容易引入 panic。
1 2 3 4 5 6 7 8 9 10
func do ( v interface {}) { n := v .( int ) // might panic } func do ( v interface {}) { n , ok := v .( int ) if ! ok { // 断言失败处理 } }
这个过程体现在下面的几个函数上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
// The assertXXX functions may fail (either panicking or returning false, // depending on whether they are 1-result or 2-result). func assertI2I ( inter * interfacetype , i iface ) ( r iface ) { tab := i . tab if tab == nil { // explicit conversions require non-nil interface value. panic (& TypeAssertionError { "" , "" , inter . typ . string (), "" }) } if tab . inter == inter { r . tab = tab r . data = i . data return } r . tab = getitab ( inter , tab . _type , false ) r . data = i . data return } func assertI2I2 ( inter * interfacetype , i iface ) ( r iface , b bool ) { tab := i . tab if tab == nil { return } if tab . inter != inter { tab = getitab ( inter , tab . _type , true ) if tab == nil { return } } r . tab = tab r . data = i . data b = true return } // 类似 func assertE2I ( inter * interfacetype , e eface ) ( r iface ) func assertE2I2 ( inter * interfacetype , e eface ) ( r iface , b bool )
4. 总结
从某种意义上来说,Golang 的 interface 也是一种多态的体现。对比其他支持多态特性的语言,实现还是略有差异,很难说谁好谁坏。
Golang配置文件解析-oozgconf
代码地址如下:http://www.demodashi.com/demo/14411.html 简介 oozgconf基于Golang开发,用于项目中配置文件的读取以及加载,是一个轻量级的配置文件工具 ...
golang xml解析
第二章里还提到了xml的解析部分.之前有想整理下encoding包下常用的几个文件格式的处理.这次刚好整理下xml的部分.先上例子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 1 ...
golang timeoutHandler解析及kubernetes中的变种
Golang里的http request timeout比较简单,但是稍不留心就容易出现错误,最近在kubernetes生产环境中出现了的一个问题让我有机会好好捋一捋golang中关于timeout中 ...
Golang ---json解析
golang官方为我们提供了标准的json解析库–encoding/json,大部分情况下,使用它已经够用了.不过这个解析包有个很大的问题–性能.它不够快,如果我们开发高性能.高并发的网络服务就无法满 ...
golang:interface{}类型测试
在golang中空的interface即interface{}可以看作任意类型, 即C中的void *. 对interface{}进行类型测试有2种语法: 1. Comma-ok断言: value, ...
Golang中解析json,构造json
json解析是如今(网络)应用程序开发中最不可或缺的一环了.许多语言需要库支持才可以解析.构造json,但Golang凭借着原生库就可以很好地做到这一点. json的基本表现形式有两个:struct与 ...
工作随笔——Golang interface 转换成其他类型
新的公司,新的氛围.一年了,打算写点什么.so,那就写google的golang语言吧. 最最最基础的语法结构见go语言菜鸟教程 接下来写点菜鸟教程没有的. go语言的设计者认为:go语言必须让程序员 ...
golang interface
接口定义 Interface类型可以定义一组方法,但是这些不需要实现.并且interface不能 包含任何变量. type Interface interface { test1(a, b int) ...
golang interface判断为空nil
要判断interface 空的问题,首先看下其底层实现. interface 底层结构 根据 interface 是否包含有 method,底层实现上用两种 struct 来表示:iface 和 ef ...
随机推荐
lvm镜像卷
镜像能够分配物理分区的多个副本,从而提高数据的可用性.当某个磁盘发生故障并且其物理分区变为不可用时,你仍然可以访问可用磁盘上的镜像数据.LVM在逻辑卷内执行镜像. 系统版本 # cat /etc/ce ...
黑马程序员IDEA版JAVA基础班\JavaWeb部分视频\2-10Request和Response\第5节 request登录案例
用户登录案例需求: 1.编写login.html登录页面 username & password 两个输入框 2.使用Druid数据库连接池技术,操作mysql,day14数据库中user表 ...
黑马_13 Spring Boot:01.spring boot 介绍&&02.spring boot 入门
13 Spring Boot: 01.spring boot 介绍&&02.spring boot 入门 04.spring boot 配置文件 SpringBoot基础 1.1 原有 ...
几个Java基础题
1.java中线程能不能重复start t1.start(); System.out.println("ssss"); t1.start(); 答:第一 ...
dubbo的超时处理和配置覆盖
提供者的设置方式 消费者的设置方式 配置原则 dubbo推荐在Provider上尽量多配置Consumer端属性: 1.作服务的提供者,比服务使用方更清楚服务性能参数,如调用的超时时间,合理的重试次数 ...
遥测数据导出sql
SELECT (select codename from CD_BGStation where CodeValue=StationCode)as 监测点位,case when [DETECTIONST ...
Apsara Clouder云计算技能认证:云数据库管理与数据迁移
一.课程介绍 二.云数据库的简介及使用场景 1.云数据库简介 1.1特点: 用户按存储容量和带宽的需求付费 可移植性 按需扩展 高可用性(HA) 1.2阿里云云数据库 RDS 稳定可靠,可弹性伸缩的在 ...
如何让网站HTTPS评级为A或者A+
环境说明:CentOS Linux release 7.5.1804 (Core).nginx/1.10.0 需求:公司网站在myssl的评级只得到了B的评分,需要提升至A+ 具体操作如下: 一.ng ...
D. Salary Changing(找中位数)
题:https://codeforces.com/contest/1251/problem/D 题意:给你n个单位需要满足达到的区间,再给个s,s是要分配给n的单位的量,当然∑l<=s,问经过分 ...
window下mysql安装步骤
1. 官网下载mysql zip包 2. 解压后再D:\database\mysql\mysql-5.7.26-winx64下添加my.ini文件,文件内容如下: [mysql]# 设置mysql客户 ...