go 接口学习笔记
这里是对接口在汇编层面上转换和实现的小结,详细了解可参考 Go 语言接口的原理
1. 类型转换:结构体到接口
1.1 结构体方法实现接口
package main
type Duck interface {
Quack()
}
type Cat struct {
Name string
}
//go:noinline
func (c Cat) Quack() {
println(c.Name + " handsome")
}
func main() {
var c Duck = Cat{Name: "lubanseven"}
c.Quack()
}
将汇编实现分为三块:
- 结构体初始化;
- 结构体到接口类型转换;
- 调用结构体方法;
1.1.1 结构体初始化
XORPS X0, X0 ;; X0 = 0
MOVUPS X0, ""..autotmp_1+48(SP) ;; StringHeader(SP+48).Data = 0
LEAQ go.string."lubanseven"(SB), AX ;; AX = &"lubanseven"
MOVQ AX, ""..autotmp_1+48(SP) ;; StringHeader(SP+48).Data = AX = &"lubanseven"
MOVQ $10, ""..autotmp_1+56(SP) ;; StringHeader(SP+56).Len = 10
示意图如下:

1.1.2 结构体到接口类型转换
LEAQ go.itab."".Cat,"".Duck(SB), AX ;; AX = itab = &(go.itab."".Cat,"".Duck)
MOVQ AX, (SP) ;; SP = AX
LEAQ ""..autotmp_1+48(SP), AX ;; AX = StringHeader(SP+48).Data
MOVQ AX, 8(SP) ;; SP + 8 = AX
CALL runtime.convT2I(SB) ;; runtime.convT2I(SP, SP+8)
查看 runtime.convT2I 函数的实现:
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
}
runtime.convT2I 函数会返回 runtime.iface 结构体,该结构体表示包含方法的接口。其中,函数内通过获取的类型分配内存空间,并将 elem 指针指向的内容拷贝到堆中。
返回的 runtime.iface 结构体将放在栈上的 SP+16 ~ SP+32 处,分别表示 iface.tab 和 iface.data。
示意图如下:

1.1.3 调用结构体方法
MOVQ 16(SP), AX
MOVQ 24(SP), CX
MOVQ AX, "".c+32(SP)
MOVQ CX, "".c+40(SP)
MOVQ "".c+32(SP), AX
MOVQ 24(AX), AX ;; AX = *AX + 24 = iface.tab.fun[0] = Cat.Quack()
MOVQ "".c+40(SP), CX ;; CX = iface.data
MOVQ CX, (SP) ;; SP = CX
CALL AX ;; CX.Quack()
其中,MOVQ 24(AX), AX 表示将 iface.tab 中指向方法 Quack() 的指针赋给 AX。由于 Duck 接口只有一个 Quack 方法,因此这里 24(AX) 索引到的即是第一个方法指针。
最后,CALL AX 传递 (SP) 的结构体值,实现 Quack() 方法的调用。
示意图如下:

1.2 结构体指针方法实现接口
package main
type Duck interface {
Quack()
}
type Cat struct {
Name string
}
//go:noinline
func (c *Cat) Quack() {
println(c.Name + " handsome")
}
func main() {
var c Duck = &Cat{Name: "lubanseven"}
c.Quack()
}
同样的,将汇编实现分为三块:
- 结构体初始化;
- 结构体到接口类型转换;
- 调用结构体方法;
1.2.1 结构体初始化
LEAQ type."".Cat(SB), AX ;; AX = &type."".Cat
MOVQ AX, (SP) ;; SP = AX = &type."".Cat
CALL runtime.newobject(SB) ;; SP + 8 = &Cat{}
MOVQ 8(SP), DI ;; DI = SP + 8
MOVQ DI, ""..autotmp_2+16(SP) ;; SP + 16 = DI
MOVQ $10, 8(DI) ;; *DI + 8 = StringHeader(DI.Name).Len = 10
LEAQ go.string."lubanseven"(SB), AX ;; AX = &"lubanseven"
MOVQ AX, (DI) ;; *DI = StringHeader(DI.Name).Data = AX
需要说明的是,LEAQ type."".Cat(SB), AX 将指向类型 Cat 的指针赋给 AX。runtime.newobject(SB) 创建结构体 Cat 的实例。通过 DI 寄存器对结构体变量赋值,注意字符串 string 的结构体实现是 StringHeader{...}。
示意图如下:

1.2.2 结构体到接口类型转换
MOVQ ""..autotmp_2+16(SP), AX
LEAQ go.itab.*"".Cat,"".Duck(SB), CX
MOVQ CX, "".c+32(SP)
MOVQ AX, "".c+40(SP)
结构体到接口类型的转换即转换为接口结构体 runtime.iface。其中,SP+32 表示 iface.tab,SP+40 表示 iface.data。SP+32 ~ SP+48 共同组成了接口结构体 runtime.iface,实现结构体 Cat 到接口类型的转换。
示意图如下:

1.2.3 调用指针接收者方法
MOVQ "".c+32(SP), AX
MOVQ 24(AX), AX
MOVQ "".c+40(SP), CX
MOVQ CX, (SP)
CALL AX
此例和 1.1.3 节类似,这里不加以描述了。
2. 类型转换:接口到结构体
除了结构体到接口的类型转换,go 也有接口到结构体类型的转换。通过类型断言可以实现,但类型断言背后做了些什么呢?
这里分空接口和非空接口两种情况查看接口到结构体类型转换。
2.1 非空接口
接口到结构体转换示例代码:
func main() {
var c Duck = &Cat{Name: "lubanseven"}
switch c.(type) {
case *Cat:
cat := c.(*Cat)
cat.Quack()
}
}
从汇编代码看 Cat 结构体和接口结构体 runtime.iface 的创建过程类似,这里忽略。直接看最关键的接口类型到结构体类型的转换过程:
00079 LEAQ go.itab.*"".Cat,"".Duck(SB), CX ;; CX = &(go.itab.*"".Cat,"".Duck)
00086 MOVQ CX, "".c+56(SP) ;; SP + 56 = CX
00101 MOVQ "".c+56(SP), CX ;; CX = SP + 56
00125 MOVL 16(CX), AX ;; AX = *CX + 16 = runtime.iface.itab.hash
00132 CMPL AX, $593696792 ;; if runtime.iface.itab.hash == $593696792 {
00137 JEQ 141
00139 JMP 236
00176 MOVQ "".c+64(SP), AX ;; AX = &Cat{Name: "lubanseven"}
00205 MOVQ AX, (SP) ;; SP = AX
00209 CALL "".(*Cat).Quack(SB) ;; SP.Quack()
00214 JMP 216
00236 JMP 228 ;; } else {
00228 JMP 230 ;;
00230 JMP 216 ;;
00216 MOVQ 104(SP), BP ;; BP = SP + 104
00221 ADDQ $112, SP ;; SP = SP + 112
00225 RET ;; }
可以看到,类型转换实际上是通过比较 runtime.iface.itab.hash 和结构体 hash 判断类型是否相等,如果相等调用结构体,实现方法调用。如果不相等,则回收函数栈空间。
2.2 空接口
对于空接口类型转换,编译器省略了将结构体转换为 runtime.eface 的过程,从汇编代码上并未看到转换过程。和非空接口逻辑类似,空接口转换也需判断 hash 值,不过空接口的 hash 从 runtime.eface._type 获取。
3. 总结:
本篇学习笔记大致介绍了接口和结构体类型的互相转换过程,通过汇编代码分析转换的底层逻辑实现知其然,知其所以然。
go 接口学习笔记的更多相关文章
- JMeter接口学习笔记2017
协议学习地址:http://www.cnblogs.com/TankXiao/archive/2012/02/13/2342672.html 本篇学习笔记来自于慕课网上学习JMeter的学习笔记 学习 ...
- PHP 开发 APP 接口 学习笔记与总结 - Redis 缓存
Redis 可以定期将数据备份到磁盘中(持久化),同时不仅仅支持简单的key/value 类型的数据,同时还提供list,set,hash等数据结构的存储:Memcache 只是简单的key/valu ...
- thinkphp5开发restful-api接口 学习笔记一
视频学习地址: http://study.163.com/course/courseMain.htm?courseId=1004171002 源码和文档(如果满意,欢迎 star): https:// ...
- 工程化编程实战callback接口学习笔记
一.编译并运行 help.version命令执行正常,但quit命令出错 二.Debug 从命令输入到执行过程: 源代码: 更改后: 运行结果:能正确运行quit命令 Callback接口学习成果: ...
- PHP 开发 APP 接口 学习笔记与总结 - APP 接口实例 [3] 首页 APP 接口开发方案 ② 读取缓存方式
以静态缓存为例. 修改 file.php line:11 去掉 path 参数(方便),加上缓存时间参数: public function cacheData($k,$v = '',$cacheTim ...
- OpenCV(C++接口)学习笔记1-图像读取、显示、保存
OpenCV在2.0加入版本号之后C++接口函数,学习前C语言的接口功能.现在OpenCV它已被发展到2.4.9版本号,所以,我决定学习C++接口函数,与步伐. 1.创建图像 cv::Mat imag ...
- .net接口学习笔记
1.接口的声明 接口的声明不能包含:数据成员,静态变量:只能包含如下类型的静态成员函数的声明:方法,属性,事件,索引器.声明中不能包含任何实现的代码,而在每个成员成名的主体后,必须使用分号. 接口声明 ...
- PHP 开发 APP 接口 学习笔记与总结 - APP 接口实例 [6] 版本升级接口开发
判定 app 是否需要加密:通过 app 表中的 status 字段来判定,加密的字符串为 app 表中的 key 字段. 在获取的客户端和服务器端(数据库表中相应字段)的版本号不一致时,返回 dat ...
- PHP 开发 APP 接口 学习笔记与总结 - APP 接口实例 [5] 版本设计分析及数据表设计
APP 版本升级以及 APP 演示 ① 版本升级分析以及数据表设计 ② 版本升级接口开发以及 APP 演示 /** * version_upgrade 版本升级信息表 */ CREATE TABLE ...
- PHP 开发 APP 接口 学习笔记与总结 - APP 接口实例 [4] 首页 APP 接口开发方案 ③ 定时读取缓存方式
用于 linux 执行 crontab 命令生成缓存的文件 crop.php <?php //让crontab 定时执行的脚本程序 require_once 'db.php'; require_ ...
随机推荐
- 推荐给前端开发的 5 款 Chrome 扩展 🚀
大家好,我是 dom 哥.这是我关于 Chrome 扩展开发的系列文章,感兴趣的可以 点个小星星. 工欲善其事,必先利其器.Chrome 可能是前端开发中使用最多的浏览器.在日常开发中,下列几款 Ch ...
- bash shell笔记整理——date命令
date命令初步了解 简单来说date的主要作用大多数用于以给定的格式来显示时间. 在后期我们写一些脚本当中也会使用到,比如说按照日期来给文件备份打包等. 下面我们来看看帮助信息: [root@ngi ...
- 华企盾DSC可能涉及的Linux命令
掌握Linux系统的基本命令 详细使用请参考:Linux命令大全 uname 查看系统信息 wget 文件下载 chmod 改变文件或目录权限 ls 显示指定工作目录下的内容及属性信息 cd 切换当前 ...
- 数字孪生结合GIS能够为城市灾害预防工作提供什么帮助?
数字孪生技术结合GIS(地理信息系统)在城市灾害预防工作中发挥着重要的作用,为城市的安全和可持续发展提供了关键帮助.数字孪生是一种将现实世界与数字虚拟世界相结合的技术,可以帮助城市规划者和管理者更好地 ...
- selenium之鼠标键盘操作
鼠标操作 1.引入ActionChains类 2.定位相关元素 3.在ActionChains().调用相关鼠标操作方法 from selenium.webdriver.common.action_c ...
- 文心一言 VS 讯飞星火 VS chatgpt (172)-- 算法导论13.3 1题
一.用go语言,在 RB-INSERT 的第 16 行,将新插人的结点 z 着为红色.注意到,如果将 z 着为黑色,则红黑树的性质4就不会被破坏.那么为什么不选择将 z 着为黑色呢? 文心一言: 在红 ...
- [Acwing 164. 可达性统计] 题解报告
事实上,这道题并不需要拓扑排序.(当然,拓扑排序还是更快) 题目分析 首先,题目中说了,这是一个有向无环图,所以,我们可以考虑 \(\texttt{DP}\) / 记搜 / 拓扑排序 来解决这道题. ...
- Java面试必考:什么是字节码?采用字节码的好处?
Java面试必考:什么是字节码?采用字节码的好处? 于哥你好,最近在java面试中被问答到什么是字节码?采用字节码的好处是什么?瞬间懵了,如果你连这个都不知道,我保证你面试GG! 首先说下Java的优 ...
- bazel 使用 gtest/gmock 报错 Constraints from @bazel_tools//platforms have been removed
问题描述 运行 bazel test 命令,遇到错误:"Constraints from @bazel_tools//platforms have been removed. Please ...
- position的属性值
(fixed的父元素永远是浏览器窗口,不会根据页面滚动而改变位置:absolute的父元素是可以设置的,他会永远跟随父元素的位置的改变而改变.) 1.position: relative;相对定位 不 ...