之前有看到一句话,很形象地描述了闭包
类是有行为的数据,为闭包是有数据的行为;
也就是说闭包是有上下文的,我们以测试例子为例,通过test函数生成的闭包函数,都有各自的a,这个a就是闭包的上下文数据,而且这个a一直伴随着他的闭包函数,每调用一次,a都会发生变化;
我们分析了上述汇编代码,来看下闭包实现原理;在这个测试例子中,由于a是闭包的上下文数据,因此a必须在堆上分配,如果在栈上分配,函数结束,a也被回收了;然后会定义出一个匿名结构体:
type.struct{
F uintptr//这个就是闭包调用的函数指针
a *int//这就是闭包的上下文数据
}
接着生成一个该对象,并将之前在堆上分配的整型对象a的地址赋值给结构体中的a指针,接下来将闭包调用的func函数地址赋值给结构体中F指针;这样,每生成一个闭包函数,其实就是生成一个上述结构体对象,每个闭包对象也就有自己的数据a和调用函数F;最后将这个结构体的地址返回给main函数;
来看下main函数获取闭包的过程;
"".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
很明显,main函数调用test函数获取的是闭包对象的地址,通过这个闭包对象地址找到闭包函数,然后执行这个闭包函数,并且把闭包对象的地址传进函数,这点和C++传this指针原理一样,为了修改成员变量a;
最后看下test内部的匿名函数(闭包函数实现):
"".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









