来源:自学PHP网 时间:2019-08-07 16:47 作者:小飞侠 阅读:次
[导读] 深入分析golang多值返回以及闭包的实现...
一、前言 golang有很多新颖的特性,不知道大家的使用的时候,有没想过,这些特性是如何实现的?当然你可能会说,不了解这些特性好像也不影响自己使用golang,你说的也有道理,但是,多了解底层的实现原理,对于在使用golang时的眼界是完全不一样的,就类似于看过http的实现之后,再来使用http框架,和未看过http框架时的眼界是不一样的,当然,你如果是一名it爱好者,求知欲自然会引导你去学习。 二、这篇文章主要就分析两点: 1、golang多值返回的实现; 2、golang闭包的实现; 三、golang多值返回的实现 我们在学C/C++时,很多人应该有了解过C/C++函数调用过程,参数是通过寄存器di和si(假设就两个参数)传递给被调用的函数,被调用函数的返回结果只能是通过eax寄存器返回给调用函数,因此C/C++函数只能返回一个值,那么我们是不是可以想象,golang的多值返回是否可以通过多个寄存器来实现的,正如用多个寄存器来传参一样? 这也是一种办法,但是golang并没有采用;我的理解是引入多个寄存器来存储返回值,会引起多个寄存器用途的重新约定,这无疑增加了复杂度;可以这么说,golang的ABI与C/C++非常不一样; 在从汇编角度分析golang多值返回之前,需要先熟悉golang汇编代码的一些约定, golang官网 有说明,这里重点说明四个symbols,需要注意的是这里的寄存器是伪寄存器: 1.FP 栈底寄存器,指向一个函数栈的顶部; 2.PC 程序计数器,指向下一条执行指令; 3.SB 指向静态数据的基指针,全局符号; 4.SP 栈顶寄存器; 这里面最重要的就是FP和SP,FP寄存器主要用于取参数以及存返回值,golang函数调用的实现很大程度上都是依赖这两个寄存器,这里先给出结果, +-----------+---\ | 返回值2 | \ +-----------+ \ | 返回值1 | \ +---------+-+ | 参数2 | 这些在调用函数中 +-----------+ | 参数1 | / +-----------+ / | 返回地址 | / +-----------+--\/-----fp值 | 局部变量 | \ | ... | 被调用数栈祯 | | / +-----------+--/+---sp值 这个就是golang的一个函数栈,也是说函数传参是通过 下面通过一个例子来分析 package main import "fmt" func test(i, j int) (int, int) { a:=i+ j b:=i- j return a,b } func main() { a,b:= test(2,1) fmt.Println(a, b) } 这个例子很简单,主要是为了说明golang多值返回的过程;我们通过下面命令编译该程序
然后,就可以打开test.s,来看下这个小程序的汇编代码。首先来看下test函数的汇编代码 "".test t=1size=32value=0args=0x20locals=0x0 0x000000000(test.go:5) TEXT"".test(SB),$0-32//栈大小为32字节 0x000000000(test.go:5)NOP 0x000000000(test.go:5)NOP 0x000000000(test.go:5)MOVQ"".i+8(FP),CX//取第一个参数i 0x000500005(test.go:5)MOVQ"".j+16(FP),AX//取第二个参数j 0x000a00010(test.go:5) FUNCDATA$0, gclocals・a8eabfc4a4514ed6b3b0c61e9680e440(SB) 0x000a00010(test.go:5) FUNCDATA$1, gclocals・33cdeccccebe80329f1fdbee7f5874cb(SB) 0x000a00010(test.go:6)MOVQCX,BX//将i放入bx 0x000d00013(test.go:6) ADDQAX,CX//i+j放入cx 0x001000016(test.go:7) SUBQAX,BX//i-j放入bx //将返回结果存入调用函数栈帧 0x001300019(test.go:8)MOVQCX,"".~r2+24(FP) //将返回结果存入调用函数栈帧 0x001800024(test.go:8)MOVQBX,"".~r3+32(FP) 0x001d00029(test.go:8)RET 由这个汇编代码可以看出来,在 但是,这里有个问题,我的变量都是int类型,为啥分配的都是8字节,这有待考证。 本来想通过查看main函数的栈帧来验证之前的结论,但是golang对小函数自动转为内联函数,因此你们可以自己编译出来看看,main函数内部是没有调用test函数的,而是将test函数的汇编代码直接拷贝进main函数执行了。 四、golang闭包的实现 之前有去看了下C++11的 packagemain import"fmt" functest(aint)func(iint)int{ returnfunc(iint)int{ a = a + i returna } } funcmain(){ f := test(1) a := f(2) fmt.Println(a) b := f(3) fmt.Println(b) } 这个例子程序很简单, 来看下 "".test t=1size=160value=0args=0x10locals=0x20 0x000000000(test.go:5) TEXT"".test(SB),$32-16 0x000000000(test.go:5)MOVQ(TLS),CX 0x000900009(test.go:5) CMPQSP,16(CX) 0x000d00013(test.go:5) JLS142 0x000f00015(test.go:5) SUBQ$32,SP 0x001300019(test.go:5) FUNCDATA$0, gclocals・8edb5632446ada37b0a930d010725cc5(SB) 0x001300019(test.go:5) FUNCDATA$1, gclocals・008e235a1392cc90d1ed9ad2f7e76d87(SB) 0x001300019(test.go:5) LEAQ type.int(SB),BX 0x001a00026(test.go:5)MOVQBX, (SP) 0x001e00030(test.go:5) PCDATA$0,$0 //生成一个int型对象,即a 0x001e00030(test.go:5)CALLruntime.newobject(SB) //8(sp)即生成的a的地址,放入AX 0x002300035(test.go:5)MOVQ8(SP),AX //将a的地址存入sp+24的位置 0x002800040(test.go:5)MOVQAX,"".&a+24(SP) //取出main函数传入的第一个参数,即a 0x002d00045(test.go:5)MOVQ"".a+40(FP),BP //将a放入(AX)指向的内存,即上述新生成的int型对象 0x003200050(test.go:5)MOVQBP, (AX) 0x003500053(test.go:6) LEAQ type.struct { F uintptr; a *int }(SB), BX 0x003c00060(test.go:6)MOVQBX, (SP) 0x004000064(test.go:6) PCDATA$0,$1 0x004000064(test.go:6)CALLruntime.newobject(SB) //8(sp)这就是上述生成的struct对象地址 0x004500069(test.go:6)MOVQ8(SP),AX 0x004a00074(test.go:6)NOP //test内部匿名函数地址存入BP 0x004a00074(test.go:6) LEAQ"".test.func1(SB),BP //将匿名函数地址放入(AX)指向的地址,即给上述 //F uintptr赋值 0x005100081(test.go:6)MOVQBP, (AX) 0x005400084(test.go:6)MOVQAX,"".autotmp_0001+16(SP) 0x005900089(test.go:6)NOP //将上述生成的整型对象a的地址存入BP 0x005900089(test.go:6)MOVQ"".&a+24(SP),BP 0x005e00094(test.go:6) CMPB runtime.writeBarrier(SB),$0 0x006500101(test.go:6)JNE$0,117 //将a地址存入AX指向内存+8, //即为上述结构体a *int赋值 0x006700103(test.go:6)MOVQBP,8(AX) //将上述结构体的地址存入main函数栈帧中; 0x006b00107(test.go:9)MOVQAX,"".~r1+48(FP) 0x007000112(test.go:9) ADDQ$32,SP 0x007400116(test.go:9)RET 之前有看到一句话,很形象地描述了闭包 类是有行为的数据,为闭包是有数据的行为; 也就是说闭包是有上下文的,我们以测试例子为例,通过 我们分析了上述汇编代码,来看下闭包实现原理;在这个测试例子中,由于 type.struct{ F uintptr//这个就是闭包调用的函数指针 a *int//这就是闭包的上下文数据 } 接着生成一个该对象,并将之前在堆上分配的整型对象 来看下 "".main t=1size=528value=0args=0x0locals=0x88 0x000000000(test.go:12) TEXT"".main(SB),$136-0 0x000000000(test.go:12)MOVQ(TLS),CX 0x000900009(test.go:12) LEAQ -8(SP),AX 0x000e00014(test.go:12) CMPQAX,16(CX) 0x001200018(test.go:12) JLS506 0x001800024(test.go:12) SUBQ$136,SP 0x001f00031(test.go:12) FUNCDATA$0, gclocals・f5be5308b59e045b7c5b33ee8908cfb7(SB) 0x001f00031(test.go:12) FUNCDATA$1, gclocals・9d868b227cedd8dd4b1bec8682560fff(SB) //将参数1(f:=test(1))放入main函数栈顶 0x001f00031(test.go:13)MOVQ$1, (SP) 0x002700039(test.go:13) PCDATA$0,$0 //调用main函数生成闭包对象 0x002700039(test.go:13)CALL"".test(SB) //将闭包对象的地址放入DX 0x002c00044(test.go:13)MOVQ8(SP),DX //将参数2(a:=f(2))放入栈顶 0x003100049(test.go:14)MOVQ$2, (SP) 0x003900057(test.go:14)MOVQDX,"".f+56(SP) //将闭包对象的函数指针赋值给BX 0x003e00062(test.go:14)MOVQ(DX),BX 0x004100065(test.go:14) PCDATA$0,$1 //这里调用闭包函数,并且将闭包对象的地址也传进 //闭包函数,为了修改a嘛 0x004100065(test.go:14)CALLDX,BX 0x004300067(test.go:14)MOVQ8(SP),BX 很明显, 最后看下 "".test.func1t=1size=32value=0args=0x10 locals=0x0 0x000000000(test.go:6) TEXT"".test.func1(SB), $0-16 0x000000000(test.go:6) NOP 0x000000000(test.go:6) NOP 0x000000000(test.go:6) FUNCDATA $0, gclocals・23e8278e2b69a3a75fa59b23c49ed6ad(SB) 0x000000000(test.go:6) FUNCDATA $1, gclocals・33cdeccccebe80329f1fdbee7f5874cb(SB) //DX是闭包对象的地址,+8即a的地址 0x000000000(test.go:6) MOVQ8(DX), AX //AX为a的地址,(AX)即为a的值 0x000400004(test.go:7) MOVQ (AX), BP //将参数i存入R8 0x000700007(test.go:7) MOVQ"".i+8(FP), R8 //a+i的值存入BP 0x000c00012(test.go:7) ADDQ R8, BP //将a+i存入a的地址 0x000f00015(test.go:7) MOVQ BP, (AX) //将a地址最新数据存入BP 0x001200018(test.go:8) MOVQ (AX), BP //将a最新值作为返回值放入main函数栈中 0x001500021(test.go:8) MOVQ BP,"".~r1+16(FP) 0x001a00026(test.go:8) RET 闭包函数的调用过程: 1、通过闭包对象地址获取闭包上下文数据a的地址; 2、接着通过a的地址获取到a的值,并与参数i相加; 3、将a+i作为最新值存入a的地址; 4、将a最新值返回给main函数; 五、总结 这篇文章简单地从汇编角度分析了golang多值返回和闭包的实现; 多值返回主要是通过fp寄存器+offset获取参数以及存入返回值实现; 闭包主要是通过在编译时生成包含闭包函数和闭包上下文数据的结构体实现; 以上就是这篇文章的全部内容,希望对大家学习或只用golang能有一定的帮助,如果有疑问大家可以留言交流。 |
自学PHP网专注网站建设学习,PHP程序学习,平面设计学习,以及操作系统学习
京ICP备14009008号-1@版权所有www.zixuephp.com
网站声明:本站所有视频,教程都由网友上传,站长收集和分享给大家学习使用,如由牵扯版权问题请联系站长邮箱904561283@qq.com