GO闭包实现底层分析
文章目录
闭包函数是指引用了自由变量的函数,如下
|
|
从代码出发
本文章分析的完整代码如下
|
|
create
函数返回了闭包函数,该闭包函数引用了自由变量c
,下面来分析该闭包的实现
create
对应的汇编分析
|
|
从汇编中可以看到
create
函数实际返回的是一个结构体指针,该结构体定义如下
|
|
这个结构体定义在编译时生成。
main
函数汇编分析
我们想要的是一个类型为func () int
的函数,但是返回了一个结构体指针,那么实际在调用的时候发生了什么呢,我们接下来继续看汇编
|
|
这里可以发现,对于闭包函数的调用和正常函数的调用有很大区别,一般函数的调用,是caller
准备好函数入参后,调用call
指令,但是对于闭包函数的调用,这里是将闭包结构体地址存入DX寄存器后,发起调用,这其实是GO语言闭包函数调用的约定。
闭包代码的汇编分析
接下来,继续看看闭包函数的代码
|
|
create.func
是我们代码中第9、10行定义的闭包函数,从汇编源码中,可以看到闭包函数的执行逻辑,获取自由变量的逻辑,是从寄存器DX中读取闭包结构体地址+偏移的方式,和一般函数的调用获取参数不一样。
疑问
到这里,我们基本已经了解了GO语言中闭包的实现原理了。
这里可以再思考一下,为什么闭包函数中对于自由变量的读取逻辑需要通过DX寄存器+偏移的方式呢,为啥不能和正常函数调用一样,通过SP+偏移的方式呢?
这是因为SP+偏移的方式,调用者需要知道被调用者入参个数及类型,这样在编译期间,编译器可以根据函数签名安排好函数栈帧布局,分配好return value
, param var
等栈帧布局,GO语言函数栈帧布局如下
但是,对于闭包来说,调用者实际并不知道闭包函数中具体引用了多少自由变量,如果调用者也使用这种方式传递函数参数,那么编译就需要去分析每一个被调用者的具体代码逻辑,增加了编译器的复杂度和实现难度。所以闭包的调用使用了一种比较取巧的方式,调用者只需要将闭包结构体地址放入DX寄存器中,在闭包的实现逻辑中,按照DX+偏移的方式去取对应的自由变量值即可。
闭包的实现,很多是编译器帮忙处理的,编译期间分析代码,捕获自由变量,生成闭包函数结构体定义等等,闭包函数结构体本身有一个类型原型,在runtime.funcval
,定义如下
|
|
验证
验证闭包结构体的生成,修改代码如下
|
|
对应create
函数的汇编如下
|
|
main
函数中的闭包调用代码
|
|
闭包的实现代码
|
|