hi, 大家好,我是 haohongfan。

可能你看过的 interface 剖析的文章比较多了,这些文章基本都是从汇编角度分析类型转换或者动态转发。不过随着 Go 版本升级,对应的 Go 汇编也发生了巨大的变化,如果单从汇编角度去分析 interface 变的非常有难度,本篇文章我会从内存分配+汇编角度切入 interface,去了解 interface 的原理。

限于篇幅 interface 有关动态转发和反射的内容,请关注后续的文章。本篇文章主要是关于类型转换。

eface

iface

eface

func main() {var ti interface{}var a int = 100ti = afmt.Println(ti)
}

这段最常见的代码,现在提出一些问题:

  • 如何查看 ti 是 eface 还是 iface ?

  • 值 100 保存在哪里了 ?

  • 如何看 ti 的真实的值的类型 ?

大部分源码分析都是从汇编入手来看的,这里也把对应的汇编贴出来

0x0040 00064 (main.go:44)    MOVQ    $100, (SP)
0x0048 00072 (main.go:44)   CALL    runtime.convT64(SB)
0x004d 00077 (main.go:44)   MOVQ    8(SP), AX
0x0052 00082 (main.go:44)   MOVQ    AX, ""..autotmp_3+64(SP)
0x0057 00087 (main.go:44)   LEAQ    type.int(SB), CX
0x005e 00094 (main.go:44)   MOVQ    CX, "".ti+72(SP)
0x0063 00099 (main.go:44)   MOVQ    AX, "".ti+80(SP)

这段汇编有下面这些特点:

  • CALL runtime.convT64(SB):将 100 作为 runtime.convT64 的参数,该函数申请了一段内存,将 100 放入了这段内存里

  • 将类型 type.int 放入到 SP+72 的位置

  • 将包含 100 的那块内存的指针,放入到 SP + 80 的位置

这段汇编从直观上来说,interface 转换成 eface 是看不出来的。这个如何观察呢?这个就需要借助 gdb 了。

再继续深究下,如何利用内存分布来验证是 eface 呢?需要另外再添加点代码。

type eface struct {_type *_typedata  unsafe.Pointer
}type _type struct {size       uintptrptrdata    uintptr // size of memory prefix holding all pointershash       uint32tflag      tflagalign      uint8fieldAlign uint8kind       uint8equal      func(unsafe.Pointer, unsafe.Pointer) boolgcdata     *bytestr        nameOffptrToThis  typeOff
}func main() {var ti interface{}var a int = 100ti = afmt.Println("type:", *(*eface)(unsafe.Pointer(&ti))._type)fmt.Println("data:", *(*int)((*eface)(unsafe.Pointer(&ti)).data))fmt.Println((*eface)(unsafe.Pointer(&ti)))
}

output:

type: {8 0 4149441018 15 8 8 2 0x10032e0 0x10e6b60 959 27232}
data: 100
&{0x10ade20 0x1155bc0}

从这个结果上能够看出来

  • eface.kind = 2, 对应着 runtime.kindInt

  • eface.data = 100

从内存上分配上看,我们基本看出来了 eface 的内存布局及对应的最终的 eface 的类型转换结果。

iface

package maintype Person interface {Say() string
}type Man struct {
}func (m *Man) Say() string {return "Man"
}func main() {var p Personm := &Man{}p = mprintln(p.Say())
}

iface 我们也看下汇编:

0x0029 00041 (main.go:24)  LEAQ    runtime.zerobase(SB), AX
0x0030 00048 (main.go:24)   MOVQ    AX, ""..autotmp_6+48(SP)
0x0035 00053 (main.go:24)   MOVQ    AX, "".m+32(SP)
0x003a 00058 (main.go:25)   MOVQ    AX, ""..autotmp_3+64(SP)
0x003f 00063 (main.go:25)   LEAQ    go.itab.*"".Man,"".Person(SB), CX
0x0046 00070 (main.go:25)   MOVQ    CX, "".p+72(SP)
0x004b 00075 (main.go:25)   MOVQ    AX, "".p+80(SP)

这段汇编上,能够看出来是有 itab 的,但是是否真的是转成了 iface,汇编上仍然反应不出来。

同样,我们继续用 gdb 查看 Person interface 确实被转换成了 iface。

关于 iface 内存布局,我们仍然加点代码来查看

type itab struct {inter *interfacetype_type *_typehash  uint32_     [4]bytefun   [1]uintptr
}type iface struct {tab  *itabdata unsafe.Pointer
}type Person interface {Say() string
}type Man struct {Name string
}func (m *Man) Say() string {return "Man"
}func main() {var p Personm := &Man{Name: "hhf"}p = mprintln(p.Say())fmt.Println("itab:", *(*iface)(unsafe.Pointer(&p)).tab)fmt.Println("data:", *(*Man)((*iface)(unsafe.Pointer(&p)).data))
}

output:

Man
itab: {0x10b3ba0 0x10b1900 1224794265 [0 0 0 0] [17445152]}
data: {hhf}

关于想继续探究 eface, iface 的内存布局的同学,可以基于上面的代码,利用 unsafe 的相关函数去看对应的内存位置上的值。

类型断言

type Person interface {Say() string
}type Man struct {Name string
}func (m *Man) Say() string {return "Man"
}func main() {var p Personm := &Man{Name: "hhf"}p = mif m1, ok := p.(*Man); ok {fmt.Println(m1.Name)}
}

我们仅关注类型断言那块内容,贴出对应的汇编

0x0087 00135 (main.go:23)    MOVQ    "".p+104(SP), AX
0x008c 00140 (main.go:23)   MOVQ    "".p+112(SP), CX
0x0091 00145 (main.go:23)   LEAQ    go.itab.*"".Man,"".Person(SB), DX
0x0098 00152 (main.go:23)   CMPQ    DX, AX

能够看出来的是:将 iface.itab 放入了 AX,将 go.itab.*"".Man,"".Person(SB) 放入了 DX,比较两者是否相等,来判断 Person 的真实类型是否是 Man。

另外一个类型断言的方式就是 switch 了,其实两者本质上没啥区别。

interface 最著名的坑的,应该就是下面这个了。

func main() {var a interface{} = nilvar b *int = nilisNil(a)isNil(b)
}func isNil(x interface{}) {if x == nil {fmt.Println("empty interface")return}fmt.Println("non-empty interface")
}

output:

empty interface
non-empty interface

为什么会这样呢?这就涉及到 interface == nil 的判断方式了。一般情况只有 eface 的 type 和 data 都为 nil 时,interface == nil 才是 true。

当我们把 b 复制给 interface 时,x._type.Kind = kindPtr。虽说 x.data = nil,但是不符合 interface == nil 的判断条件了。

关于 interface 源码阅读的一点建议

关于 interface 源码阅读的一点建议,如果想利用汇编看源码的话,尽量选择 go1.14.x。

选择 Go 汇编来看 interface,基本上也是为了查看 interface 最终被转换成 eface 还是 iface,调用了 runtime 的哪些函数,以及对应的函数栈分布。如果 Go 版本选择的太高的话,go 汇编变化太大了,可能汇编上就看不到对应的内容了。

欢迎关注公众号。更多学习学习资料分享,关注公众号回复指令:

  • 回复 0,获取 《Go 面经》

  • 回复 1,获取 《Go 源码流程图》

Go interface 类型转换原理剖析相关推荐

  1. 解密 Go interface 的类型转换原理

    hi, 大家好,我是 haohongfan. 可能你看过的 interface 剖析的文章比较多了,这些文章基本都是从汇编角度分析类型转换或者动态转发.不过随着 Go 版本升级,对应的 Go 汇编也发 ...

  2. KVO 从基本使用到原理剖析

    文章目录 1. 简介 2. 基本使用 2.1 设置观察者 2.2 接收属性改变消息 2.3 移除观察者 2.4 KVO 使用实例 3. 原理剖析 3.1 KVO 的实现 3.2 NSKVONotify ...

  3. java校验框架源码解析_Spring Boot原理剖析和源码分析

    Spring Boot原理剖析和源码分析 依赖管理 问题一:为什么导入dependency时不需要指定版本? spring-boot-starter-parent依赖 org.springframew ...

  4. iPhone/Mac Objective-C内存管理教程和原理剖析

    版权声明 此文版权归作者Vince Yuan (vince.yuan#gmail.com)所有.欢迎非营利性转载,转载时必须包含原始链接http://vinceyuan.cnblogs.com/,且必 ...

  5. 深入理解Go底层原理剖析 (送书)

    互联网迅猛发展的数十年时间里,不断面领着各种新的场景与挑战,例如大数据.大规模集群计算.更复杂的网络环境.多核处理器引起对于高并发的需求,云计算,上千万行的服务器代码-- 那些成熟但上了年纪的语言没能 ...

  6. 『Go 语言底层原理剖析』文末送书

    互联网迅猛发展的数十年时间里,不断面领着各种新的场景与挑战,例如大数据.大规模集群计算.更复杂的网络环境.多核处理器引起对于高并发的需求,云计算,上千万行的服务器代码-- 那些成熟但上了年纪的语言没能 ...

  7. ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)

    在上一节(ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行)中提到ASP.NET Core WebApp 必须含有Startup类,在本节中将重点讲解Startup类以及Midd ...

  8. 基本功 | Litho的使用及原理剖析

    1. 什么是Litho? Litho是Facebook推出的一套高效构建Android UI的声明式框架,主要目的是提升RecyclerView复杂列表的滑动性能和降低内存占用.下面是Litho官网的 ...

  9. 断点续传的原理剖析与实例讲解

    断点续传的原理剖析与实例讲解 本文所要讲的是Android断点续传的内容,以实例的形式进行了详细介绍. 一.断点续传的原理 其实断点续传的原理很简单,就是在http的请求上和一般的下载有所不同而已. ...

最新文章

  1. MySQL Innodb日志机制深入分析
  2. Wireshark使用技巧:提取VOIP通话中的音频流
  3. 干货!神经网络原来是这样和数学挂钩的
  4. Postman 调试技巧
  5. 买淘宝特价版,产业升级的证明
  6. 定时清理日志文件-python实现
  7. Pandas高级教程之:统计方法
  8. 物联网通信协议_自动化机器上的物联网网关的目的是什么?
  9. ubuntu16.04安装java环境
  10. gitlab搭建与使用
  11. 数据中心如何选择机柜
  12. 紫光展锐回应“春藤510只支持NSA”:错误解读 SA和NSA一个都不少
  13. 基于swing的java系统_Java实验--基于Swing的简单的歌曲信息管理系统(一)
  14. java多分支流程图_Java 流程控制 之 分支结构(条件判断)
  15. MATLAB矩阵基本运算
  16. 罗斯蒙特8712ESR1A1N0M4流量变送器
  17. 《人工智能狂潮》读后感——什么是人工智能?(一)
  18. 让你的微信小程序对用户更加友好:上拉加载和下拉刷新就是关键
  19. 对于等待事件(direct path read)的理解
  20. liferay的几个配置

热门文章

  1. oracle解析select,oracle_select语句例子解析
  2. 文件表单带数据一起提交spring_基于 Spring 实现管道模式的最佳实践
  3. 对于linux下指令的进一步扩充与巩固
  4. 前端必知必会--JSON.stringify()犀利的第三个参数
  5. 使用 husky 和 lint-staged 检查 Node.js 的代码一致性
  6. 电脑无故弹出yyy102.html网页的解决办法(没办法,今天中招了)
  7. C++编程技巧—对数运算实现
  8. 微信营销这么做,你就成功了 转载
  9. xcode6是否导入framework
  10. 安装memcached服务和PECL关于memcache的两个PHP扩展