Lua 5.0实现 4 函数和闭包

当Lua编译一个函数时,会生成一个原型(prototype),该原型包含了函数的虚拟机指令,常量(数字,字符串等),和一些调试信息。在运行期,任何时候Lua执行一个function...end表达式,都会创建一个新的闭包(closure)。每一个闭包有一个与之相应的原型的引用,一个环境的引用(一个保存全局变量的表),以及一个包含了upvalue的引用的数组,通过该数组可以访问外层函数的局部变量。

作用域(即变量的生存期)以及first-class函数给如何在内嵌函数中访问外部函数的局部变量出了一个众所周知的难题。考虑图3的例子。当add2被调用时,会访问外部函数的局部变量x(在Lua中函数的参数被当做局部变量处理)。但是,当add2被调用时,创建add2的函数add已经返回。如果x是在栈中被创建的,栈中存放x的地方已经被销毁。

first-class函数:函数Lua里面被当做变量处理,也就是说可以在函数里面定义函数。

clip_image002

许多过程语言,通过限制作用域(如Python),或者不提供first-class函数(如Pascal),或者两者都做(如C)来避免这个难题。函数式语言没有这个限制。像Scheme和ML这样的非纯函数语言已经在如何编译闭包的技术上有了广泛的研究。然而他们都没有考虑限制编译器的复杂度。例如,Scheme编译器的优化器Bigloo的控制流分析模块是Lua实现的10倍:Bigloo 2.6f的控制流分析有106350行,vs. Lua5.0的核心有10155行。正如第二节说的那样,Lua需要一种更简单的实现。

Lua使用一种叫upvalue的结构来实现闭包。任何对外层局部变量的访问都间接地通过访问upvalue完成。upvalue一开始指向栈中变量存储的地方(图4,左)。当变量离开作用域的时候,upvalue将变量迁移到upvalue的槽中(图4,右)。(浅拷贝)因为upvalue的值是通过一个指针来访问的,所以这种迁移对于访问upvalue中变量的代码来说是透明的。与内嵌函数不同,声明变量的函数通过栈来访问该变量。

clip_image004

为了保证闭包之间在频繁改变的栈中能够正确地共享外层变量,每一个变量至多创建一个upvalue并在其他闭包需要时重用该upvalue。为了保证这种唯一性,Lua用一个链表(图4中的pending vars list)来保存栈中的所有open upvalue(就是仍然指向栈中数据的upvalue)。当Lua创建一个闭包的时候,会扫描所有外层局部变量。对于每个变量,如果相应的upvalue已经在open upvalue链表中,就复用它。否则,Lua会为这个变量创建一个新的upvalue并保存在open upvalue链表中。注意在open upvalue链表中查找对应的upvalue通常会在探测少数节点后就结束,因为对于每个会被内嵌函数用到的局部变量,链表最多只包含一个访问入口。一旦一个close upvalue没有被任何闭包使用,最终会被垃圾回收。

(外层函数返回的时候,open upvalue链表为空)

一个函数有可能访问更外层的局部变量。这种情况下,甚至创建闭包的时候,变量就可能不在栈中了。Lua通过使用flat闭包来解决这个问题。有了flat闭包,当一个函数访问一个不在外一层函数的栈中的外层局部变量时,就会去外一层的闭包中查找。这样,当一个函数被创建的时候,闭包中的所有变量,不是在外一层函数的栈中,就在外一层函数的闭包中。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值