这里是对接口在汇编层面上转换和实现的小结,详细了解可参考 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. 结构体初始化;
  2. 结构体到接口类型转换;
  3. 调用结构体方法;

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. 结构体到接口类型转换;
  3. 调用结构体方法;

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 接口学习笔记的更多相关文章

  1. JMeter接口学习笔记2017

    协议学习地址:http://www.cnblogs.com/TankXiao/archive/2012/02/13/2342672.html 本篇学习笔记来自于慕课网上学习JMeter的学习笔记 学习 ...

  2. PHP 开发 APP 接口 学习笔记与总结 - Redis 缓存

    Redis 可以定期将数据备份到磁盘中(持久化),同时不仅仅支持简单的key/value 类型的数据,同时还提供list,set,hash等数据结构的存储:Memcache 只是简单的key/valu ...

  3. thinkphp5开发restful-api接口 学习笔记一

    视频学习地址: http://study.163.com/course/courseMain.htm?courseId=1004171002 源码和文档(如果满意,欢迎 star): https:// ...

  4. 工程化编程实战callback接口学习笔记

    一.编译并运行 help.version命令执行正常,但quit命令出错 二.Debug 从命令输入到执行过程: 源代码: 更改后: 运行结果:能正确运行quit命令 Callback接口学习成果: ...

  5. PHP 开发 APP 接口 学习笔记与总结 - APP 接口实例 [3] 首页 APP 接口开发方案 ② 读取缓存方式

    以静态缓存为例. 修改 file.php line:11 去掉 path 参数(方便),加上缓存时间参数: public function cacheData($k,$v = '',$cacheTim ...

  6. OpenCV(C++接口)学习笔记1-图像读取、显示、保存

    OpenCV在2.0加入版本号之后C++接口函数,学习前C语言的接口功能.现在OpenCV它已被发展到2.4.9版本号,所以,我决定学习C++接口函数,与步伐. 1.创建图像 cv::Mat imag ...

  7. .net接口学习笔记

    1.接口的声明 接口的声明不能包含:数据成员,静态变量:只能包含如下类型的静态成员函数的声明:方法,属性,事件,索引器.声明中不能包含任何实现的代码,而在每个成员成名的主体后,必须使用分号. 接口声明 ...

  8. PHP 开发 APP 接口 学习笔记与总结 - APP 接口实例 [6] 版本升级接口开发

    判定 app 是否需要加密:通过 app 表中的 status 字段来判定,加密的字符串为 app 表中的 key 字段. 在获取的客户端和服务器端(数据库表中相应字段)的版本号不一致时,返回 dat ...

  9. PHP 开发 APP 接口 学习笔记与总结 - APP 接口实例 [5] 版本设计分析及数据表设计

    APP 版本升级以及 APP 演示 ① 版本升级分析以及数据表设计 ② 版本升级接口开发以及 APP 演示 /** * version_upgrade 版本升级信息表 */ CREATE TABLE ...

  10. PHP 开发 APP 接口 学习笔记与总结 - APP 接口实例 [4] 首页 APP 接口开发方案 ③ 定时读取缓存方式

    用于 linux 执行 crontab 命令生成缓存的文件 crop.php <?php //让crontab 定时执行的脚本程序 require_once 'db.php'; require_ ...

随机推荐

  1. 关于C#接口的用法详细解答,附上案例说明!

    接口 C#中的接口是一种定义了一组方法.属性和事件的类型.它只包含成员的声明,而不包含任何实现.接口可以被类通过实现的方式使用,从而使类能够具有接口定义的行为. 接口在C#中被定义为使用interfa ...

  2. RabbitMQ入门到进阶

    1.MQ简介 MQ 全称为 Message Queue,是在消息的传输过程中保存消息的容器.多用于分布式系统 之间进行通信. 2.为什么要用 MQ 1.流量消峰 没使用MQ 使用了MQ 2.应用解耦 ...

  3. C和C++练习

    要点: 1.数组 2.冒泡排序BubbleSort 3.带指针的结构体(malloc,free) 4.字符串操作(拷贝.逆序.比较) 5.格式化输出printf,sprintf 6.格式化输入,sca ...

  4. scrapy 请求meta参数使用案例-豆瓣电影爬取

    num = 0 import scrapy from scrapy.http import HtmlResponse from scrapy_demo.items import DoubanItem ...

  5. 数字孪生系统如何整合CesiumJS?整合后会产生怎样的化学反应?

    数字孪生有关的项目中,智慧城市一直是一个比较重要的类型,但是这类智慧城市项目往往包含了大量的GIS相关数据,例如倾斜摄影.DEM.DOM.地形数据等.这时,将GIS系统融合进数字孪生系统的需求就出现了 ...

  6. 2023年度低代码平台企业TOP50榜单公布—以开源起家的JeecgBoot格外亮眼

    近日,中国科学院主管.科学出版社主办的国家级核心期刊<互联网周刊>联合eNet研究院.德本咨询评选的<2023低代码企业50强>榜单正式公布.这一榜单的公布引起了业内外的广泛关 ...

  7. 春秋云镜 - CVE-2022-30887

    多语言药房管理系统 (MPMS) 是用 PHP 和 MySQL 开发的, 该软件的主要目的是在药房和客户之间提供一套接口,客户是该软件的主要用户.该软件有助于为药房业务创建一个综合数据库,并根据到期. ...

  8. 深入了解RC4 Drop加密技术

    一.引言 在网络安全领域,加密技术始终是重中之重.随着计算机技术的发展,加密算法也在不断更新换代.RC4(Rivest Cipher 4)加密算法因其高效.简洁的特性,在信息安全领域得到了广泛的应用. ...

  9. 案例解析关于ArkUI框架中ForEach的潜在陷阱与性能优化

    本文分享自华为云社区<深入解析ForEach的潜在陷阱与性能优化:错误用法与性能下降的案例分析>,作者:柠檬味拥抱 . 在ArkUI框架中,ForEach接口是基于数组类型数据进行循环渲染 ...

  10. Rust布道者张汉东倾授,入门Rust初学者都要攻破哪些难点?

    摘要:Rust语言学习曲线过于陡峭?初学者看懂这张思维导图,快速入门. Rust语言这两年的热度大家有目共睹,作为一个有着突破性变革意义的语言,其光鲜背后也有诸多质疑,对于想要在系统编程语言上更上一层 ...