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_ ...
随机推荐
- 13 HTTP传输大文件的方法
目录 如何在有限的带宽下高效快捷传输大文件? 数据压缩 分块传输 范围请求 多段数据 如何在有限的带宽下高效快捷传输大文件? 数据压缩 分块传输 范围请求 多段数据 数据压缩 思路:把大文件整体变小 ...
- offline RL | Pessimistic Bootstrapping (PBRL):在 Q 更新中惩罚 uncertainty,拉低 OOD Q value
论文题目:Pessimistic Bootstrapping for Uncertainty-Driven Offline Reinforcement Learning,ICLR 2022,6 6 8 ...
- Win10遇到服务器启动失败 80端口被占用如何解决
Win10提示"服务器启动失败,80端口被占用"怎么办?具体解决方法如下 步骤如下: 1.以管理员身份运行cmd; 2.输入:net stop http 注:如果提示是否真的需要停 ...
- shell中 << EOF 和 EOF 使用
转载请注明出处: EOF(End of File)在Shell中通常用于指示输入的结束,并在脚本或命令中进行多行输入.它允许用户指定一个特定的分界符来表示输入的结束,通常用于创建临时文件.重定向输入或 ...
- MinIO客户端之ping
MinIO提供了一个命令行程序mc用于协助用户完成日常的维护.管理类工作. 官方资料 mc ping 检查指定的MinIO节点的服务是否可用. 不带参数,命令如下: ./mc ping local1 ...
- Jenkins 系列:Jenkins 安装(Windows、Mac、Centos)和简介
目录 简介 发展历史 应用场景 Jenkins 安装部署 先决条件 硬件要求 软件包下载 war 包部署 linux 系统部署 mac 系统部署 windows 系统部署 安装后基本配置 解锁 自定义 ...
- Java的特性、内容和环境的配置
Java的特性和优势 简单性 面向对象 可移植性 高性能 分布式 动态性 多线程 安全性 健壮性 JDK包含JRE包含JVM JDK:Java Development Kit JRE:Java Run ...
- [ACTF2020 新生赛]Exec 1
[ACTF2020 新生赛]Exec 1 审题 发现题目有ping功能,猜测是命令执行漏洞. 知识点 linux系统命令 解题 先ping127.0.0.1,观察是否正常执行. 发现正常后执行ls / ...
- vue模板的首次渲染,和重新渲染,有哪些区别?
搞明白这个,能帮助我们理解开发中出现的很多问题. 一.我们先来回顾一下vue模板的渲染过程: (1)执行render函数,生成虚拟DOM. render函数是根据render.templete.el这 ...
- 文心一言 VS 讯飞星火 VS chatgpt (178)-- 算法导论13.4 1题
一.用go语言,在执行 RB-DELETE-FIXUP 之后,证明:树根一定是黑色的. 文心一言: 在证明 RB-DELETE-FIXUP 之后,树根一定是黑色的的过程中,我们首先需要理解红黑树的性质 ...