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 汇编变化太大了,可能汇编上就看不到对应的内容了。

以上分享来自,京东资深架构师 haohongfan ,大佬最近也开了公众号记录和分享他的经验总结,点击下方名片关注。

也欢迎关注我的公众号,我会一直为大家持续推荐优秀的技术文章和面经。

解密 Go interface 的类型转换原理相关推荐

  1. Golang interface 接口详细原理和使用技巧

    文章目录 Golang interface 接口详细原理和使用技巧 一.Go interface 介绍 interface 在 Go 中的重要性说明 interface 的特性 interface 接 ...

  2. 第41课:Checkpoint彻底解密:Checkpoint的运行原理和源码实现彻底详解

    第41课:Checkpoint彻底解密:Checkpoint的运行原理和源码实现彻底详解 一:Checkpoint到底是什么? 1,  Spark在生产环境下经常会面临Tranformations的R ...

  3. 区块链背后的信息安全(4)RSA加解密及签名算法的技术原理及其Go语言实现

    # RSA加解密及签名算法的技术原理及其Go语言实现 对称加密中,加密和解密使用相同的密钥,因此必须向解密者配送密钥,即密钥配送问题. 而非对称加密中,由于加密和解密分别使用公钥和私钥,而公钥是公开的 ...

  4. php椭圆曲加密,兄弟连区块链教程区块链信息安全3椭圆曲线加解密及签名算法的技术原理一...

    区块链教程区块链信息安全3椭圆曲线加解密及签名算法的技术原理一,2018年下半年,区块链行业正逐渐褪去发展之初的浮躁.回归理性,表面上看相关人才需求与身价似乎正在回落.但事实上,正是初期泡沫的渐退,让 ...

  5. Go interface 类型转换原理剖析

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

  6. springMVC4(9)属性编辑器剖析入参类型转换原理

    我们通过Http请求提交的参数都以字符串的形式呈现,但最终在springMVC的方法入参中,我们却能得到各种类型的数据,包括Number.Boolean.复杂对象类型.集合类型.Map类型等,这些都是 ...

  7. java 类型转换原理_9.java数据类型的转换

    java数据类型的转换 Java中可以进行不同数据类型的加减乘除运算吗?是可以的.在算术运算符中已经体验过如果两个整数(int)相除会去掉小数部分.如果需要保留小数部分,可以让除数或者被除数变为dou ...

  8. 解密AI芯片的加速原理

    网上对AI芯片的剖析实在太少,这里对一些论文和大佬的研究做一个总结,希望对读者有所帮助. AI 芯片的诞生 讲到半导体,不得不从摩尔定律说起.从Intel创始人戈登·摩尔提出摩尔定律到现在已经53年了 ...

  9. C语言:int型数据向char型数据的强制类型转换原理

    int型数据向char型数据强制转换原理 C语言中int是四个字节,char是1个字节,占字节多的int型向字节少的char型强制类型转换的原理就是字节截断. 转换时系统会自动删除高位的三个字节,只留 ...

最新文章

  1. 变换为json类型却遭遇乱码\u516c\u5f00\u65e5\u671f
  2. java中sleep()、wait()相同与不同详解
  3. STM32关于BOOT0和BOOT1设置,去掉Debug后完成硬件独立运行。
  4. OpenJudge计算概论-找和为K的两个元素
  5. qmoc文件_手动生成MOC文件
  6. linux 多源代码文件编译
  7. spring整合mybatis是如何配置事务的?
  8. 软件过程改进之百科名片
  9. centos7.4二进制安装mysql
  10. 云盘运用了计算机技术,360云盘咋找出来
  11. 关闭WPS广告弹窗骚扰(Kingsoft Office 推荐)
  12. html5体感游戏开发,使用HTML5开发Kinect体感游戏
  13. pscc2018安装服务器无响应,win10系统无法安装ps cc2018提示Microsoft visualc++ 2017的解决方法...
  14. 零和对策matlab,零和对策
  15. 自编码器与堆叠自编码器简述
  16. 因为发现很多家长其实没有真的明白美音和英音的区别,所以写了这篇文章
  17. (Python)Numpy矩阵增加/减少一个维度
  18. RAD Studio 11.2 详细图文安装教程 (delphi 11.2)
  19. 语言学与计算机,计算机与乔姆斯基语言学_刘俐李
  20. matlab root什么意思,root是什么意思?

热门文章

  1. 嵌入式 uboot引导kernel,kernel引导fs【转】
  2. ffmpeg,rtmpdump和nginx rtmp实现录屏,直播和录制
  3. 查看mysql错误日志定位mysql错误
  4. Bluetooth handsfree 和 headset 区别
  5. SQL Server 2005大小写敏感设置
  6. 网络强制消费案例剖析
  7. Python培训 之五 条件判断
  8. IntelliJ IDEA给Serializable类加上自动的serialVersionUID
  9. 写一个简单易用可扩展vue表单验证插件(vue-validate-easy)
  10. 使用meta来刷新网页效果