前面说到函数执行完毕,也就是闭包退出时,会清除其作用域的局部变量。但是有个问题就是保存闭包的上值,何为上值,举个简单的例子:

local f = function(a)
                return function() 
                          a = a + 1 
                          return a 
                       end 
          end

local up = f(12)
local n1 = up()
local n2 = up()

正确的执行结果应该是n1 = 13, n2 = 14,如果你懂闭包应该能得出这个结果。f中的匿名函数中变量a的作用域,没有随着其函数和父函数执行完毕而结束。最开始实现闭包的时候,在退出函数时并不清除其局部变量,而是在函数开始的时候清除。这样虽然也能实现闭包,但是会造成内存泄漏,以及内存占用严重,影响性能。要知道lua是一门函数式语言,函数是第一类值(first class),函数的调用几乎是它的全部,所以这种方式并不可取。

后来重新设计了保存闭包上值得方法,生成闭包时生成一个上值变量的表,当然在退出函数时,仍要清除局部变量,即删掉局部变量表中的那些值。闭包说白了就是函数中有函数,考虑函数中函数变量作用域问题,怎么知道一个闭包有上值呢?

要知道函数里面可以定义函数,函数也可以返回函数。函数中有函数的情况:

function f()
        local a = 123
        function ff()
            a = a + 1
        end
        ff()
end

这个例子中ff函数的变量a本身是函数f的变量,所以他是一个上值,我们必须在ff中保存f的作用域。这个好像不是什么大的问题,子函数ff是可以引用到父函数f的作用域的。
我们再来看另一个返回函数的例子:

local f = function()
    local a = 123
    return function() a = a + 1 return a end 
end
local ff = f()
local n = ff()

如果当f()执行后,清除f()局部变量,即a失效,那么当执行ff()时,势必找不到a了。执行函数后不清除局部变量又会导致资源浪费。所以必须找到一个稳妥的办法来解决这个问题。

一个好的办法是在嵌套子函数里面新建一个表来保存其上值。关键是怎么知道一个函数是嵌套的子函数呢?在解析函数时看其是不是在另一个函数的函数体中,这样就可以保存闭包的上值了。但不是所有的子嵌套函数需要保存其上值,例如上面的第一个例子,子函数只能在父函数中执行,不需要保存上值,只有函数返回的函数才需要。所以我们也可以在return语句上做文章。在返回语句中对栈上的元素判断是不是闭包,再来保存其上值。但是如果有多个返回值,需要操作栈,取栈的值,这样破坏了只取栈顶的优雅,所以决定不管子嵌套函数在父函数的哪个部分,都保存其上值。

需要注意的是,子嵌套函数的上值什么时候清除,这也是个问题,可能需要垃圾回收,这个以后考虑。


项目地址:
https://github.com/shonm520/mlua