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

本文基于 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)

我们缺少了循环的主体,接下来,我们看看这部分的指令:

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

第一条指令 MOVQ ""..autotmp_5+16(SP)(AX*8), DX 表示 「将内存从源位置移动到目标地址」,它由以下几个部分组成:

""..autotmp_5+16(SP) 表示 slice ,而 SP 表示了栈指针即我们当前的内存空间, autotmp_* 是自动生成变量名。

  • 偏差为 8 是因为在 64 位计算机架构中, int 类型是 8 字节的。
  • 偏差乘以寄存器 AX 的值,表示当前循环中的位置。
  • 寄存器 DX 代表的目标地址内包含着循环的当前值。

之后, INCQ 表示自增,然后会增加循环的当前位置:

循环主体的最后一条指令是 ADDQ DX, CX ,表示把 DX 的值加在 CX ,所以我们可以看出, DX 所包含的值是目前循环所代表的的值,而 CX 代表了变量 t 的值。

他会一直循环至计数器到 5 ,之后循环体之后的指令表示为将寄存器 CX 的值赋予 t

0x0058 00088 (main.go:11)   MOVQ   CX, "".t+8(SP)

以下为最终状态的示意图:

我们可以完善 Go 中循环的转换:

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

 var tmp int

 goto end
start:
 tmp = l[i]
 i++
 t += tmp
end:
 if i < 5 {
 goto start
 }

 println(t)
}

这个程序生成的汇编代码与上文所提到的函数生成的汇编代码有着相同的输出。

改进

循环的内部转换方式可能会对其他特性(如 Go 调度器)产生影响。在 Go 1.10 之前,循环像下面的代码一样编译:

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

 var tmp int
 p := uintptr(unsafe.Pointer(&l[0]))

 if i >= 5 {
 goto end
 }
body:
 tmp = *(*int)(unsafe.Pointer(p))
 p += unsafe.Sizeof(l[0])
 i++
 t += tmp
 if i < 5 {
 goto body
 }
end:
 println(t)
}

这种实现方式的问题是,当 i 达到 5 时,指针 p 已经超过了内存分配空间的尾部。这个问题使得循环不容易抢占,因为它的主体是不安全的。循环编译的优化确保它不会创建任何越界的指针。这个改进是为 Go 调度器中的非合作抢占做准备的。你可以在这篇Proposal中到更详细的讨论。

到此这篇关于Go 中的循环是如何转为汇编的(方法详解)的文章就介绍到这了,更多相关go 循环汇编内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 汇编分析 Golang 循环(推荐)

    女主宣言 今天小编为大家分享一篇关于Golang循环汇编分析的文章,文章中介绍了golang循环的汇编层面的处理,通过分析,我们可以更了解循环的实现.希望能对大家有所帮助. PS:丰富的一线技术.多元化的表现形式,尽在" 3 60云计算 ",点关注哦! 循环是编程中很强大的一个概念,而且非常容易处理. 但是,必须将其翻译成机器可理解的基本指令. 它的编译方式也可能影响标准库中的其他组件. 让我们开始分析一下范围循环 . 1循环汇编 范围循环可以迭代数组,切片或通道.下面函数展示了,对分

  • 从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

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

    本文基于 Go 1.13 版本 循环在编程中是一个重要的概念,且易于上手.但是,循环必须被翻译成计算机能理解的底层指令.它的编译方式也会在一定程度上影响到标准库中的其他组件.让我们开始分析循环吧. 循环的汇编代码 使用循坏迭代 array , slice , channel ,以下是一个使用循环对 slice 计算总和的例子. func main() { l := []int{9, 45, 23, 67, 78} t := 0 for _, v := range l { t += v } pri

  • JS中call/apply、arguments、undefined/null方法详解

    a.call和apply方法详解 -------------------------------------------------------------------------------- call方法: 语法:call([thisObj[,arg1[, arg2[, [,.argN]]]]]) 定义:调用一个对象的一个方法,以另一个对象替换当前对象. 说明: call 方法可以用来代替另一个对象调用一个方法.call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指

  • C#实现将PDF转为Excel的方法详解

    目录 dll引用方法 方法1 方法2 PDF转Excel C# VB.NET 通常,PDF格式的文档能支持的编辑功能不如office文档多,针对PDF文档里面有表格数据的,如果想要编辑表格里面的数据,可以将该PDF文档转为Excel格式,然后编辑.本文,将以C#代码为例,介绍如何实现由PDF格式到Excel文档格式的转换.下面是具体步骤. dll引用方法 方法1 在程序中引入Spire.Pdf.dll文件:将 Spire.PDF for .NET 下载到本地,解压,安装.安装完成后,找到安装路径

  • 关于java中可变长参数的定义及使用方法详解

    JAVA中可以为方法定义可变长参数( Varargs)来匹配不确定数量的多个参数,其定义用"..."表示.其实,这类似于为方法传了一个数组,且在使用方法上也和数组相同,如下: public void test(String... str){ for(String s : str){ } } 调用方法就像普通的调用方式相同,只不过可以匹配的参数为0到多个.如下: test(); test("lilei"); test("lilei","h

  • 用Newtonsoft将json串转为对象的方法(详解)

    首先,将json串转为一个JObject对象: JObject jo = (JObject)JsonConvert.DeserializeObject(CurrentSelectedItemReq); 这个JObject是一个"值对"类型,比如说我们的json串是这样: { "rows":[ [ {"NumIid":"001"}, {"PicUrl":"xxx.png"}, {"

  • 对python中xlsx,csv以及json文件的相互转化方法详解

    最近需要各种转格式,这里对相关代码作一个记录,方便日后查询. xlsx文件转csv文件 import xlrd import csv def xlsx_to_csv(): workbook = xlrd.open_workbook('1.xlsx') table = workbook.sheet_by_index(0) with codecs.open('1.csv', 'w', encoding='utf-8') as f: write = csv.writer(f) for row_num

  • 对Python中一维向量和一维向量转置相乘的方法详解

    在Python中有时会碰到需要一个一维列向量(n*1)与另一个一维列向量(n*1)的转置(1*n)相乘,得到一个n*n的矩阵的情况.但是在python中, 我们发现,无论是".T"还是"np.transpose"都无法实现一维向量的转置,相比之下,Matlab一句" a' "就能实现了. 那怎么实现呢?我找了个方法.请看: 即,我们把向量reshape一下,如此便实现了一维向量与一维向量转置相乘为矩阵的目的. 若大家有其他方法望告知. 以上这篇对

  • 在Pycharm中对代码进行注释和缩进的方法详解

    一.注释 1. #单行注释 2. """ 多行注释 """ 3. pycharm多行注释快捷键:Ctrl+/ 二.缩进 缩进:Tab 反向缩进:Shift+Tab 以上这篇在Pycharm中对代码进行注释和缩进的方法详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们.

  • 对numpy中二进制格式的数据存储与读取方法详解

    使用save可以实现对numpy数据的磁盘存储,存储的方式是二进制.查看使用说明,说明专门提到了是未经压缩的二进制形式.存储后的数据可以进行加载或者读取,通过使用load方法. In [81]:np.save('demo',data1) 通过以上操作,数据data1被存储到了demo文件中,numpy会自动加上npy的文件后缀名. In [82]: a =np.load('demo.npy') In [83]: a Out[83]: array([0,1, 2, 3, 4, 5, 6, 7, 8

  • c++中的单例类模板的实现方法详解

     1.什么是单例模式 在架构设计时,某些类在整个系统生命周期中最多只能有一个对象存在 ( Single Instance ).如超市收银系统,其外观主要由显示器(1个).扫描枪(1个).收款箱(1个)组成,在系统正常运行期间这三部分都是唯一存在的:也就是说,显示器.扫描枪.收款箱这三部分都应该有各自的类,并且每个类只能唯一地实例化一个对象,基于这种模式的程序设计,称为单例模式. !!!单例模式只能创建一个对象,且该对象的生命周期伴随系统的整个运行期间. 2.怎么实现单例模式 思考:如何定义一个类

随机推荐