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

 更新时间:2020年02月22日 20:02:44   转载 作者:西山居  
Go常用的遍历方式有两种:for和for-range。这篇文章主要介绍了从Go汇编角度解读for循环的两个疑点,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下

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在汇编层面的伪代码:

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等)。

延申阅读

A Quick Guide to Go's Assembler

到此这篇关于从Go汇编角度解读for循环的文章就介绍到这了,更多相关汇编for循环内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 一位数乘法的汇编语言实现方法

    一位数乘法的汇编语言实现方法

    这篇文章主要介绍了一位数乘法的汇编语言实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • 汇编语言指令集之串处理指令讲解

    汇编语言指令集之串处理指令讲解

    这篇文章主要介绍了汇编语言指令集之串处理指令讲解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2020-01-01
  • 汇编语言实现电子闹钟思路详解

    汇编语言实现电子闹钟思路详解

    这篇文章主要介绍了汇编语言实现电子闹钟思路详解,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-02-02
  • 汇编语言编译环境搭建过程

    汇编语言编译环境搭建过程

    这篇文章主要介绍了汇编语言编译环境搭建过程,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-01-01
  • 汇编语言伪指令和汇编指令的区别

    汇编语言伪指令和汇编指令的区别

    指令是控制程序运行时的机器代码运作的,是CPU执行的依据,编程、编译、执行都是有效的。伪指令不直接控制运行时刻的机器,但是控制翻译程序如何生成机器指令代码,感兴趣的朋友跟随小编一起看看吧
    2020-01-01
  • 汇编语言中的segment

    汇编语言中的segment

    segment是段的意思,是段定义伪指令,一个正常的应用程序被由若干个 segment组成,接下来通过本文给大家介绍汇编语言中的segment,需要的朋友可以参考下
    2020-01-01
  • 汇编语言学习心得

    汇编语言学习心得

    汇编语言(assembly language)是一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。这篇文章主要介绍了汇编语言学习心得,需要的朋友可以参考下
    2020-01-01
  • 汇编语言中cmp指令用法笔记与总结

    汇编语言中cmp指令用法笔记与总结

    这篇文章主要介绍了汇编语言中cmp指令用法,结合实例形式总结分析了汇编语言cmp指令基本功能、使用方法及操作注意事项,需要的朋友可以参考下
    2020-01-01
  • 汇编中的数组分配和指针的实现代码

    汇编中的数组分配和指针的实现代码

    这篇文章主要介绍了汇编中的数组分配和指针的实现代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-01-01
  • 汇编 函数调用的实现

    汇编 函数调用的实现

    这篇文章主要介绍了汇编 函数调用的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02

最新评论