设为首页 加入收藏

TOP

深度解密Go语言之Slice(二)
2019-04-02 00:08:34 】 浏览:854
Tags:深度 解密 语言 Slice
utotmp_1+48(SP) 0x007c 00124 (main.go:8)MOVQ $0, ""..autotmp_1+56(SP) 0x0085 00133 (main.go:8)LEAQ type.[]int(SB), AX 0x008c 00140 (main.go:8)MOVQ AX, (SP) 0x0090 00144 (main.go:8)LEAQ ""..autotmp_2+64(SP), AX 0x0095 00149 (main.go:8)MOVQ AX, 8(SP) 0x009a 00154 (main.go:8)PCDATA $0, $1 0x009a 00154 (main.go:8)CALL runtime.convT2Eslice(SB) 0x009f 00159 (main.go:8)MOVQ 16(SP), AX 0x00a4 00164 (main.go:8)MOVQ 24(SP), CX 0x00a9 00169 (main.go:8)MOVQ AX, ""..autotmp_1+48(SP) 0x00ae 00174 (main.go:8)MOVQ CX, ""..autotmp_1+56(SP) 0x00b3 00179 (main.go:8)LEAQ ""..autotmp_1+48(SP), AX 0x00b8 00184 (main.go:8)MOVQ AX, (SP) 0x00bc 00188 (main.go:8)MOVQ $1, 8(SP) 0x00c5 00197 (main.go:8)MOVQ $1, 16(SP) 0x00ce 00206 (main.go:8)PCDATA $0, $1 0x00ce 00206 (main.go:8)CALL fmt.Println(SB) 0x00d3 00211 (main.go:9)MOVQ 88(SP), BP 0x00d8 00216 (main.go:9)ADDQ $96, SP 0x00dc 00220 (main.go:9)RET 0x00dd 00221 (main.go:7)PCDATA $0, $0 0x00dd 00221 (main.go:7)CALL runtime.panicindex(SB) 0x00e2 00226 (main.go:7)UNDEF 0x00e4 00228 (main.go:7)NOP 0x00e4 00228 (main.go:5)PCDATA $0, $-1 0x00e4 00228 (main.go:5)CALL runtime.morestack_noctxt(SB) 0x00e9 00233 (main.go:5)JMP 0

先说明一下,Go 语言汇编 FUNCDATAPCDATA 是编译器产生的,用于保存一些和垃圾收集相关的信息,我们先不用 care。

以上汇编代码行数比较多,没关系,因为命令都比较简单,而且我们的 Go 源码也足够简单,没有理由看不明白。

我们先从上到下扫一眼,看到几个关键函数:

CALL    runtime.makeslice(SB)
CALL    runtime.convT2Eslice(SB)
CALL    fmt.Println(SB)
CALL    runtime.morestack_noctxt(SB)
序号 功能
1 创建slice
2 类型转换
3 打印函数
4 栈空间扩容

1是创建 slice 相关的;2是类型转换;调用 fmt.Println需要将 slice 作一个转换; 3是打印语句;4是栈空间扩容函数,在函数开始处,会检查当前栈空间是否足够,不够的话需要调用它来进行扩容。暂时可以忽略。

调用了函数就会涉及到参数传递,Go 的参数传递都是通过 栈空间完成的。接下来,我们详细分析这整个过程。

行数 作用
1 main函数定义,栈帧大小为 96B
2-4 判断栈是否需要进行扩容,如果需要则跳到 228,这里会调用 runtime.morestack_noctxt(SB) 进行栈扩容操作。具体细节后续还会有文章来讲
5-9 caller BP 压栈,具体细节后面会讲到
10-15 调用 runtime.makeslice(SB) 函数及准备工作。*_type表示的是 int,也就是 slice 元素的类型。这里对应的源码是第6行,也就是调用 make 创建 slice 的那一行。510 分别代表长度和容量,函数参数会在栈顶准备好,之后执行函数调用命令 CALL,进入到被调用函数的栈帧,就会按顺序从 caller 的栈顶取函数参数
16-18 接收 makeslice的返回值,通过 move 移动到寄存器中
19-21 给数组索引值为 2 的元素赋上值 2,因为是 int 型的 slice,元素大小为8字节,所以 MOVQ $2, 16(AX) 此命令就是将 2 搬到索引为 2 的位置。这里还会对索引值的大小进行检查,如果越界,则会跳转到 221,执行 panic 函数
22-26 分别通过寄存器 AX,CX,DXmakeslice 的返回值 move 到内存的其他位置,也称为局部变量,这样就构造出了 slice

makeslice 栈帧

左边是栈上的数据,右边是堆上的数据。array 指向 slice 的底层数据,被分配到堆上了。注意,栈上的地址是从高向低增长;堆则从低向高增长。栈左边的数字表示对应的汇编代码的行数,栈右边箭头则表示栈地址。(48)SP、(56)SP 表示的内容接着往下看。

注意,在图中,栈地址是从下往上增长,所以 SP 表示的是图中 *_type 所在的位置,其它的依此类推。

行数 作用
27-32 准备调用 runtime.convT2Eslice(SB)的函数参数
33-36 接收返回值,通过AX,CX寄存器 move 到(48)SP、(56)SP

convT2Eslice 的函数声明如下:

func convT2Eslice(t *_type, elem unsafe.Pointer) (e eface) 

第一个参数是指针 *_type_type是一个表示类型的结构体,这里传入的就是 slice的类型 []int;第二个参数则是元素的指针,这里传入的就是 slice 底层数组的首地址。

返回值 eface 的结构体定义如下:

type eface struct {
    _type *_type
    data  unsafe.Pointer
}

由于我们会调用 fmt.Println(slice),看下函数原型:

func Println(a ...interface{}) (n int, err error)

Println 接收 interface 类型,因此我们需要将 slice 转换成 interface 类型。由于 slice 没有方法,是个“空 interface”。因此会调用 convT2Eslice 完成这一转换过程。

convT2Eslice 函数返回的是类型指针和数据地址。源码就不贴了,大体流程是:调用 mallocgc 分配一块内存,把数据 copy 进到新的内存,然后返回这块内存的地址,*_type 则直接返回传入的参数。

convT2Eslice 栈帧

32(SP)40(SP) 其实是 makeslice 函数的返回值,这里可以忽略。

还剩 fmt.Println(slice) 最后一个函数调用了,我们继续。

行数 作用
37-4
首页 上一页 1 2 3 4 5 6 下一页 尾页 2/6/6
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇go语言模版编程 下一篇WaitGroup

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目