从Go汇编角度解读for循环的问题

2020-02-22 21:57:59刘景俊

Go常用的遍历方式有两种:for和for-range。实际上,for-range也只是for的语法糖,本文试图从汇编代码入手解释for循环是如何工作的。

问题

首先来看看几个令人迷惑的地方。

问题1:遍历过程中取值

func main() {
 arr := [5]int{1, 2, 3, 4, 5}
 for _, v := range arr {
  println(&v)
 }
}

上面这段代码里,会打印出什么?

问题2:遍历过程中修改

arr := []int{1, 2, 3, 4, 5}
for v := range arr {
 arr = append(arr, v)
}

上面这段代码里,遍历前后arr有哪些变化?

窥探虚实

对于问题1,我们期待会打印出5个不同的地址,实际上最终打印出来的都是同一个地址,我们可以猜测v在循环过程中只声明了一次。看看问题1的汇编代码:

0x0028 00040 (main.go:4)  MOVQ ""..stmp_0(SB), AX
0x002f 00047 (main.go:4)  MOVQ AX, "".arr+24(SP)
0x0034 00052 (main.go:4)  MOVUPS ""..stmp_0+8(SB), X0
0x003b 00059 (main.go:4)  MOVUPS X0, "".arr+32(SP)
0x0040 00064 (main.go:4)  MOVUPS ""..stmp_0+24(SB), X0
0x0047 00071 (main.go:4)  MOVUPS X0, "".arr+48(SP)
0x004c 00076 (main.go:5)  MOVQ "".arr+24(SP), AX
0x0051 00081 (main.go:5)  MOVQ AX, ""..autotmp_2+64(SP)
0x0056 00086 (main.go:5)  MOVUPS "".arr+32(SP), X0
0x005b 00091 (main.go:5)  MOVUPS X0, ""..autotmp_2+72(SP)
0x0060 00096 (main.go:5)  MOVUPS "".arr+48(SP), X0
0x0065 00101 (main.go:5)  MOVUPS X0, ""..autotmp_2+88(SP)
0x006a 00106 (main.go:5)  XORL AX, AX
0x006c 00108 (main.go:5)  JMP  162
0x006e 00110 (main.go:5)  MOVQ AX, ""..autotmp_7+16(SP)
0x0073 00115 (main.go:5)  MOVQ ""..autotmp_2+64(SP)(AX*8), CX
0x0078 00120 (main.go:5)  MOVQ CX, "".v+8(SP)
0x007d 00125 (main.go:6)  CALL runtime.printlock(SB)
0x0082 00130 (main.go:6)  LEAQ "".v+8(SP), AX
0x0087 00135 (main.go:6)  MOVQ AX, (SP)
0x008b 00139 (main.go:6)  CALL runtime.printpointer(SB)
0x0090 00144 (main.go:6)  CALL runtime.printnl(SB)
0x0095 00149 (main.go:6)  CALL runtime.printunlock(SB)
0x009a 00154 (main.go:5)  MOVQ ""..autotmp_7+16(SP), AX
0x009f 00159 (main.go:5)  INCQ AX
0x00a2 00162 (main.go:5)  CMPQ AX, $5
0x00a6 00166 (main.go:5)  JLT  110

00040行:MOVQ ""..stmp_0(SB), AX将stmp_0变量里的内容放到AX寄存器里,stmp_0实际上就是arr数组,在生成的汇编代码里:

""..stmp_0 SRODATA size=40
  0x0000 01 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00
  0x0010 03 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00
  0x0020 05 00 00 00 00 00 00 00

由此可以看到stmp_0正是arr数组。

00106行:XORL AX AX是初始化AX寄存器,AX寄存器里包含当前循环位置。 00108行:JMP 162表示跳转到00162行。 00162行:CMPQ AX $5比较寄存器AX和5,伪代码:i < 5,如果满足条件,则跳转到00110行。

00110行00159行为循环体代码,注意到00159行INCQ AX, 意即AX寄存器值自增,到这里我们可以大致分析出来for-range在汇编层面的伪代码: