博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Go语言模型:通过runtime源码和汇编看interface的底层实现
阅读量:4207 次
发布时间:2019-05-26

本文共 2295 字,大约阅读时间需要 7 分钟。

Go的汇编

要看懂Go的内存模型,需要对Go runtime的源码和Go的汇编指令有一定的了解。Go的汇编是基于 的风格。Go的实现有大量的汇编代码,比如:goroutine的上下文切换肯定是要汇编的,切换栈帧和寄存器,这些是无法通过简单的function call来完成的,操作系统的线程上下文切换同样类似。

在Linux平台,可以通过看Go runtime的源码结合GDB(v7.1+)调试来快速理解一些Go关键的内存模型和运行原理,从而更加深刻的理解Go的语言特性。

Go的接口

在Go 1.10版中,Go interface 的实现在 runtime2.go 中,定义如下:

// 可以利用Goland IDE双击shift快速搜索到type iface struct {
tab *itab // 有类型信息、函数指针表等 data unsafe.Pointer // 类似C的void*}// layout of Itab known to compilers// allocated in non-garbage-collected memory// Needs to be in sync with// ../cmd/compile/internal/gc/reflect.go:/^func.dumptypestructs.type itab struct {
inter *interfacetype _type *_type link *itab bad int32 unused int32 fun [1]uintptr // variable sized}

通过上面的源码,我们可以看到Go的接口类型本质也是一个结构体,里面是两个指针,一个指向类型+函数等信息,一个指向数据信息。那么显而易见,简单来说,一个结构体实现了一个接口,把这个结构体变量赋值给这个接口变量,那么显然这个接口变量里的俩指针,就完成了数据和实现的绑定。

一个小例子:

// @file: hi.gopackage mainimport (	"fmt")type Printer interface {
Print()}type User struct {
id int name string}func (u *User) Print() {
fmt.Println(u.id, u.name)}func main() {
a := &User{
id: 1, name: "Michel", } var m Printer = a m.Print()}

编译时带上-gcflags "-N -l"忽略优化,然后用gdb分析如下:

go build -gcflags "-N -l" hi.gogdb hi   // gdb 拉起进程, 断main.main然后next到接口变量m赋值,或者直接断对应行(gdb) set pr pr on // 优化输出// 接口的数据域(gdb) p m$4 = {
tab = 0x4c3a20
, data = 0xc42007c020 // 结构数据指针 == 对应结构体指针a}(gdb) p a$5 = (struct main.User *) 0xc42007c020// 接口的函数表(gdb) p m.tab$6 = (runtime.itab *) 0x4c3a20
(gdb) p *m.tab$7 = {
inter = 0x49b740, _type = 0x4991c0, hash = 1834220034, _ = "\000\000\000", fun = {
4727024} // hex(4727024) = 0x00000000004820f0, 函数的入口地址}(gdb) s // step in 这个函数,看入口地址main.(*User).Print (u=0xc42007c020) at /media/sf_VMshare/go_workspace/src/hi.go:1616 func (u *User) Print() {
(gdb) disassDump of assembler code for function main.(*User).Print:=> 0x00000000004820f0 <+0>: mov %fs:0xfffffffffffffff8,%rcx// 接口赋值的地方的反汇编25 var m Printer = a=> 0x00000000004822d8 <+120>: mov %rax,0x20(%rsp) 0x00000000004822dd <+125>: lea 0x4173c(%rip),%rcx # 0x4c3a20
0x00000000004822e4 <+132>: mov %rcx,0x28(%rsp) 0x00000000004822e9 <+137>: mov %rax,0x30(%rsp)

这样,通过看Go runtime的源码,加上gdb调试,就很容易理解接口的底层实现原理了,进一步的Go进程内存布局也可以用同样方法深入查看。

参考

转载地址:http://mbqli.baihongyu.com/

你可能感兴趣的文章
linux shell界面变成灰色,输入左移动输出[D^
查看>>
c++ 知识点(不断更新)
查看>>
通过MXnet理解LayerNorm,InstanceNorm
查看>>
解决 Ubuntu 1804 安装 php7.2 后出现未定义的 curl_init 错误
查看>>
Ubuntu16.04安装php5.6
查看>>
F1计算公式
查看>>
numpy对向量进行排序,输出最大的前几位
查看>>
Mxnet 网络可视化(MobileNetV2)
查看>>
实现Confluence6.* 破解
查看>>
c++ static和extern声明全局变量区别
查看>>
int *const p,const int *p和int const *p的区别
查看>>
ubuntu 解析不了域名—ubuntu的DNS配置
查看>>
git创建新的branch分支
查看>>
git submodule的使用
查看>>
php7.0 卸载
查看>>
nmon监控与 nmon analyser分析
查看>>
Linux设置支持中文UTF8字符集
查看>>
matplotlib 画曲线图2
查看>>
Mac 安装zsh步骤
查看>>
python 通过logging记录INFO和DEBUG记录
查看>>