go tool compile -S test.go > test.s
然后,就可以打开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
由这个汇编代码可以看出来,在test函数内部,是通过fp+8取第一个参数,fp+16取第二个参数;然后将返回的第一个值存入fp+24,返回的第二个值存入fp+32,和我上述所说完全一致;golang函数调用过程,是通过fp+offset来实现传参和返回值,而不像C/C++都是通过寄存器实现传参和返回值;
但是,这里有个问题,我的变量都是int类型,为啥分配的都是8字节,这有待考证。
本来想通过查看main函数的栈帧来验证之前的结论,但是golang对小函数自动转为内联函数,因此你们可以自己编译出来看看,main函数内部是没有调用test函数的,而是将test函数的汇编代码直接拷贝进main函数执行了。
四、golang闭包的实现
之前有去看了下C++11的lambda函数的实现,其实实现原理就是仿函数;编译器在编译lambda函数时,会生成一个匿名的仿函数类,然后执行这个lambda函数时,会调用编译生成的匿名仿函数类重载函数调用方法,这个方法也就是lambda函数中定义的方法;其实golang闭包的实现和这个类似,我们通过例子来说明
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函数传入一个整型参数a,返回一个函数类型;这个函数类型传入一个整型参数以及返回一个整型值;main函数调用test函数,返回一个闭包函数。
来看下test函数的汇编代码:
"".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









