Go 中的循环是如何转为汇编的(方法详解)

2020-05-06 12:24:21王振洲

本文基于 Go 1.13 版本

循环在编程中是一个重要的概念,且易于上手。但是,循环必须被翻译成计算机能理解的底层指令。它的编译方式也会在一定程度上影响到标准库中的其他组件。让我们开始分析循环吧。

循环的汇编代码

使用循坏迭代 arrayslicechannel ,以下是一个使用循环对 slice 计算总和的例子。

func main() {
 l := []int{9, 45, 23, 67, 78}
 t := 0

 for _, v := range l {
 t += v
 }

 println(t)
}

使用 go tool compile -S main.go 生成的汇编代码,以下为相关输出:

0x0041 00065 (main.go:4)   XORL   AX, AX
0x0043 00067 (main.go:4)   XORL   CX, CX

0x0045 00069 (main.go:7)   JMP    82
0x0047 00071 (main.go:7)   MOVQ   ""..autotmp_5+16(SP)(AX*8), DX
0x004c 00076 (main.go:7)   INCQ   AX
0x004f 00079 (main.go:8)   ADDQ   DX, CX
0x0052 00082 (main.go:7)   CMPQ   AX, $5
0x0056 00086 (main.go:7)   JLT    71
0x0058 00088 (main.go:11)  MOVQ   CX, "".t+8(SP)

我把这些指令分为了两个部分,初始化部分和循环主体。前两条指令,将两个寄存器初始化为零值。

0x0041 00065 (main.go:4)   XORL   AX, AX
0x0043 00067 (main.go:4)   XORL   CX, CX

寄存器 AX 包含着当前循环所处位置,而 CX 包含着变量 t 的值,下面为带有指令和通用寄存器的直观表示:

循环从表示「跳转到指令 82 」的 JMP 82 开始,这条指令的作用可以通过第二行来判断:

接下来的指令 CMPQ AX,$5 表示「比较寄存器 AX5 」,事实上,这个操作是把 AX 中的值减去 5 ,然后储存在另一个寄存器中,这个值可以被用在下一条指令 JLT 71 中,它的含义是 「如果值小于 0 则跳转到指令 71 」,以下是更新后的直观表示:

如果不满足条件,则程序将会跳转到循环体之后的下一条指令执行。

所以,我们现在有了对循环的基本框架,以下是转换后的 Go 循环:

goto end
start:
 ?
end:
 if i < 5 {
 goto start
 }

println(t)