逆向分析Lua语言特性的设计与实现(一):闭包

来源:互联网 发布:toast js插件 编辑:程序博客网 时间:2024/05/16 07:07
一、Lua闭包
若将一个函数写在另外一个函数内部,那么这个位于内部的函数便可以访问外部函数中的局部变量,这个特性称之为closure,中文翻译为“闭包”。

二、实践分析闭包的实现
(一)、实例1:Lua指令逆向分析
function newCounter()local i = 0    return function()        i = i+1       return i   endendc1= newCounter()print(c1())print(c1())

ChunkSpry交互模式反编译:
>function newCounter() local i = 0 return function() i = i+1; return i end end; source chunk: (interactive mode); x86 standard (32-bit, little endian, doubles); function [0] definition (level 1); 0 upvalues, 0 params, 2 stacks.function  0 0 2 2.const  "newCounter"  ; 0; function [0] definition (level 2); 0 upvalues, 0 params, 2 stacks.function  0 0 0 2.local  "i"  ; 0.const  0  ; 0; function [0] definition (level 3)     //匿名函数:closure; 1 upvalues, 0 params, 2 stacks.function  1 0 0 2.upvalue  "i"  ; 0.const  1  ; 0[1] getupval   0   0        ; i     //R(0):=UpValue(0)[2] add        0   0   256  ; 1    //i=i+1[3] setupval   0   0        ; i     //UpValue(0):=R(0),将寄存器0中的值写回UpValue(0)[4] getupval   0   0        ; i     //再次取出UpValue(0),为解下来的返回i做准备[5] return     0   2[6] return     0   1; end of function[1] loadk      0   0        ; 0[2] closure    1   0        ; 1 upvalues[3] move       0   0[4] return     1   2[5] return     0   1; end of function[1] closure    0   0        ; 0 upvalues[2] setglobal  0   0        ; newCounter[3] return     0   1; end of function>
从上述Lua字节码可以看出,局部变量在闭包函数中是通过upvalue传递的。
在匿名函数中,第一条指令[1]操作是从该函数的upvalue列表中,0号索引位置上得到局部变量i,并拷贝在第0号寄存器中。
在执行期间,upvalue是由CLOSURE设置并由虚拟机维护的。
在pascal中,外层作用域中的变量是通过遍历栈帧找到的。然而,Lua函数是第一类值,可以被赋值给变量在别处引用。Lua虚拟机的解决办法是通过
getupval   和setupval   提供访问upvalue的干净接口,不过upvalue的管理是虚拟机自己处理的。
事实上,Lua编译一个函数时,会为他生成一个原型(prototype),其中包含了函数体对应的虚拟机指令、函数用到的常量值(数,文本字符串等等)和一些调试信息。
在运行时,每当Lua执行一个形如function...end 这样的表达式时,他就会创建一个新的数据对象,其中包含了相应函数原型的引用、环境(environment,用来查找全局变量的表)的引用及一个由所有upvalue引用组成的数组,而这个数据对象就称为闭包。由此可见,函数是编译期概念,是静态的,而闭包是运行期概念,是动态的。
upvalue实际是局部变量,而局部变量是保存在函数堆栈框架上(stack frame)的,所以只要upvalue还没有离开自己的作用域,他就一直生存在函数堆栈上。这种情况下,闭包将通过指向堆栈上的upvalue的引用来访问他们,一旦upvalue即将离开自己的作用域(这也意味着他马上要从堆栈中消失),闭包就会为他分配空间并保存当前的值,以后便可通过指向新分配空间的引用来访问该upvalue。

(二)、实例2:

function func1(n)
     local function func2()
          print(n)
     end

     n = n + 1000
     return func2
end

g1=func1(1)
g1() //打印出1还是1001?

func1将返回内部定义的一个函数
g1=func1(1),使g1得到了第一类值的函数
g1()这条语句实质上执行了一次这个函数,那么print(n)这条语句打印的英国是1呢还是1001呢?为什么呢?
通过运行程序,我们发现奇怪的问题:内嵌函数定义在n = n + 1000这条语句之前,可为什么g1()打印出的却是10001?
upvalue实际是局部变量,而局部变量是保存在函数堆栈框架上(stack frame)的,所以只要upvalue还没有离开自己的作用域,他就一直生存在函数堆栈上。这种情况下,闭包将通过指向堆栈上的upvalue的引用来访问他们,一旦upvalue即将离开自己的作用域(这也意味着他马上要从堆栈中消失),闭包就会为他分配空间并保存当前的值,以后便可通过指向新分配空间的引用来访问该upvalue。当执行到g1(),既func1(1)的n = n + 1000时,闭包已创建了,不过n并没有离开作用域,所以闭包仍然引用堆栈上的n,当return func2完成时,n即将结束生命,此时闭包便将n(已是1001了)复制到自己管理的空间中以便将来访问。弄清晰了内部的秘密后,运行结果就不难解释了。

三、闭包的设计
lua源代码闭包部分欣赏
未完待续...

原创粉丝点击