for i := 0; i < 5; i++ {
}
这也就验证了上面说的for-range只是普通for的语法糖。
00110到00120行是循环体代码的前半部分。从Go 汇编文档上看:SP寄存器指向当前栈帧的局部变量的开始位置,也就是说局部变量放在了SP寄存器的栈帧里。
00115行:MOVQ ""..autotmp_2+64(SP)(AX*8), CX,autotmp_*是为临时变量自动生成的名字,这行汇编做的事情是将某个v值(注意,是值)放在CX寄存器里。
00120行:MOVQ CX, "".v+8(SP)将CX寄存器里的内容放在SP寄存器指向的位置,00125行代码是一个隔断,00125之后的代码与println有关。重点在这行代码,每次循环都会将值放在"".v+8(SP)这个位置,在这个循环体代码里,我们并没有看到其他的临时变量声明,到这里,我们可以总结出:"".v+8(SP)这个位置就是变量v在栈帧中的位置,由于位置一直没有发生变化,在进行&v操作时取到的会是同一个地址。
对于问题1,根据汇编代码的分析,我们得出结论:v在循环过程中只会声明一次,每次循环只是将v值替换,并未重新声明临时变量,这样解释了问题1代码的输出结果。
再回到问题2,我们期待循环永远不会停下来,但实际上循环5次之后停了下来。我们有理由猜测:循环体中的arr与arr = append(arr, v)中的并非同一个。
由于两段代码的汇编代码差不多,这里仍以上面的汇编代码来分析。00106行是初始AX寄存器,也是循环的开始,所以我们关注00106行之前的代码。
根据上面的分析,在00040行已经将数组内容放到了AX寄存器里,00081行到00101行,将数组拷贝到autotmp_2变量内,由SP所指向的栈顶。
在读这段代码的汇编时,发现编译器针对数组内容做了一个小优化,当数组长度小于5时候,编译器会认为这个数组只是临时变量,会直接做栈上赋值,直接将数组内容放到autotmp_2变量中(栈上),省略了从数据只读区到AX的过程(即00040行),数组长度小于5时,汇编代码如下:
0x0024 00036 (main.go:5) MOVQ $1, ""..autotmp_2+24(SP) 0x002d 00045 (main.go:5) MOVQ $2, ""..autotmp_2+32(SP) 0x0036 00054 (main.go:5) MOVQ $3, ""..autotmp_2+40(SP) 0x003f 00063 (main.go:5) XORL AX, AX
分析到这里,我们可以得到一段表示for循环的伪代码:
temp := {1, 2, 3, 4, 5}
for i := 0; i < 5; i++ {
v := temp[i]
}
由此我们可以得到结论:for-range时拷贝了被访问的列表(array、slice、hashmap等)。问题2所带的思考:当数组比较大时,for-range拷贝数组的开销也会比较大,在实际应用中应当避免这个开销。
总结
从上面的汇编代码分析过来看,总结两点:
1. 循环过程中位置变量,只会声明一次,也就是说每次循环位置变量的地址都是相同的。 2. for-range时拷贝了被访问的列表(array、slice、hashmap等)。










