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), AXstmp_0变量里的内容放到AX寄存器里,stmp_0实际上就是arr数组,在生成的汇编代码里:

""..stmp_0 SRODATA size=400x0000 01 00 00 00 00 00 00 00 02 00 00 00 00 00 00 000x0010 03 00 00 00 00 00 00 00 04 00 00 00 00 00 00 000x0020 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次之后停了下来。我们有理由猜测:循环体中的arrarr = 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: How Are Loops Translated to Assembly

shell for循环两个变量并列_从Go汇编角度解释for循环的两个疑点相关推荐

  1. Shell脚本基础 、 使用变量 、 条件测试及选择 、 列表式循环案例

    Shell脚本基础 . 使用变量 . 条件测试及选择 . 列表式循环案例 1 案例1:Shell脚本的编写及测试 1.1 问题 1.2 方案 1.3 步骤 2 案例2:重定向输出的应用 2.1 问题 ...

  2. shell中文件路径用变量定义_环境变量

    环境变量可以使得 bash shell 存储有关 shell 会话和工作环境的信息,允许在内存中存储数据. 在 bash shell 中,环境变量分为: 全局变量 局部变量 全局变量对 shell 会 ...

  3. 两个变量相乘_自动控制原理-信号流图与系统状态变量传递函数之间联系如此紧密...

    自动控制原理-信号流图与系统状态变量传递函数之间联系如此紧密 实例分析: 带有正反馈,负反馈,信号线交叉的结构图简化: 比较点ab之间是一个并联方框连接,引出点1夹在两个比较点a,b之间,G1G2无法 ...

  4. C++在循环内和循环外定义变量的差异(如何写出高效的for循环)

    写这篇文章的原因是我在问答平台看到的一个问题: C++内层循环中定义变量和在外面定义比影响大吗? 问题来自:http://ask.csdn.net/questions/176270 例如: for(i ...

  5. java循环依赖问题怎么解决_[DENGUES]如何解决插件之间的循环依赖问题

    如果两个插件出现双向的独立性关联就形成了循环依赖,Dengues利用Eclipse扩展点方式解决这个问题. 首先来介绍一下原理.Eclipse在启动的时候会将所有的扩展点实现加载到一个注册表里面,这里 ...

  6. css使两个盒子并列_你需掌握的CSS知识都在这了(长文建议收藏,文末有福利)...

    (给前端大学加星标,提升前端技能.) 作者:深圳湾码农 https://juejin.im/post/5d8336d2f265da03df5f4a06 1.CSS盒模型,在不同浏览器的差异 css 标 ...

  7. css使两个盒子并列_前端学习CSS

    一 CSS CSS是Cascading Style Sheets的缩写,层叠样式表,用来控制网页数据的显示,可以使网页的显示与数据内容分离. 二 引入方式 (1)行内式:在标记的style属性中设置C ...

  8. java 循环里声明变量赋值_在Java中声明变量外部Foreach循环

    有人可以请赐教我以下事项: public class Loopy { public static void main(String[] args) { int[] myArray = {7,6,5,4 ...

  9. css使两个盒子并列_盒子模型(重点)

    所谓盒子模型就是把HTML页面中的元素看作是一个矩形的盒子,也就是一个盛装内容的容器.每个矩形都由元素的内容.内边距(padding).边框(border)和外边距(margin)组成. 看透网页布局 ...

最新文章

  1. PyTorch 51.BatchNorm和Dropout层的不协调现象
  2. 【LeetCode】55.跳跃游戏
  3. WEP自动破解工具wesside-ng
  4. PAT甲级1070 Mooncake:[C++题解]贪心
  5. android软件的data使用方法,实例讲解Android中SQLiteDatabase使用方法
  6. Linux安装Nginx使用负载均衡
  7. 最优化学习笔记(二十)——全局搜索算法
  8. matlab实现均衡变换,MATLAB如何实现图像增强灰度变换直方图均衡匹配
  9. 【转】C++从零实现神经网络
  10. simulink快捷键_从EPB模型谈谈Simulink代码生成
  11. wireshark 分析过滤数据
  12. 设计模式---访问者模式(C++实现)
  13. python爬虫设计实验
  14. 请问,“什么知识才是值得学习的?”
  15. 鸿蒙发布会重播,华为鸿蒙发布会2021现场直播在线观看/回放入口
  16. 华为新员工入职180天详细培训计划
  17. android 高德地图定位圈,android ------ 实现高德定位并获取相应信息 ( 最新版高德SDK 和 Android SDK版本)...
  18. 【C语言趣味教程】typedef 真爽不爽不要玩 | 初识结构体
  19. 研究生周报(第十九周)
  20. 工程流体力学笔记暂记24 (不可压缩粘性流体的运动微分方程**N-S方程**)

热门文章

  1. CG-光栅图形学区域填充算法-学习笔记
  2. 排球计分程序重构(五中篇)
  3. 初学Python01
  4. Android的Button监听
  5. Longest Valid Parentheses leetcode java
  6. 易错点:C 语言 continue while for 循环
  7. 26、Windows API Shell编程(2)
  8. 如果估算IT的测试Pattern数
  9. Windows中,文件所在路径查找命令
  10. 测量不确定度matlab,基于MATLAB用蒙特卡洛法评估测量不确定度简介,目录书摘