闭包是Lua语言编程一个重要而又常用的概念。它主要作用是在函数离开作用域后还可以访问外部的临时变量,这些变量称为upvalue。
闭包分为两种,分别CClosure和LClosure。它们都被封装到一个Closure结构体里,CClosure和LClosure都有一个ClosureHeader的结构体。结构体ClosureHeader的字段作用:
1.isC: 区分是哪一种闭包类型。0是LClosure, 1是CClosure
2.nupvalues:记录upvalue的数量。
#define ClosureHeader \
CommonHeader; lu_byte isC; lu_byte nupvalues; GCObject *gclist; \
struct Table *env
typedef struct CClosure {
ClosureHeader;
lua_CFunction f;
TValue upvalue[1];
} CClosure;
typedef struct LClosure {
ClosureHeader;
struct Proto *p;
UpVal *upvals[1];
} LClosure;
typedef union Closure {
CClosure c;
LClosure l;
} Closure;
这里我们主要分析LClosure的情况。例如以下创建闭包的代码
function test()
local a = 1
local function callback()
print(a)
end
return callback
end
local cb = test()
cb()
在调用test函数时,创建一个local变量和一个callback函数,callback可以访问外部的a。当调用test的时候,此时栈上会创建a,然后创建callback这个闭包。
创建闭包的过程:
1.获取函数开始到闭包创建前的upvalue数量,这里只有a,数量为1.
2.创建闭包会根据upvalue的数量来创建,upvalue的数量会影响闭包的创建大小。
3.闭包LClosure创建好了之后,需要去创建UpValue,即闭包内可以访问的外部变量,UpValue会和openvalue链成一条双向链表,此时LuaState的openvalue会指向最新的UpValue,UpValue为open 状态。open 状态指的是:upvalue的值还在栈里,当闭包要从栈里pop掉的时候,UpValue会变成close状态(稍后再说)。
4.创建闭包的时候,这个时候内存图为:
假如现在test函数执行完毕,返回callback函数,这时候栈会把LClosure Pop出来。Pop出来的时候,LClosure指向的UpValue会把指向的值设置到自己的结构体上,并把UpValue指向自己结构TValue上,此时即为Close状态。同时UpValue会从openvalue的链表上移除。此时的内存图为:
闭包和变量从栈中移除后,闭包还能访问原来的变量,但是它并不在栈上,而是被拷贝闭包一直指向的UpValue去,这就是为什么闭包和变量被Pop掉后还能访问并修改外部变量的原因。
现在我们去想想,假如test函数内出现了2个闭包,都需要访问和修改UpValue a的话,会怎么样呢?来看下面的代码和结果
这里面闭包callback0是打印a的,闭包callback1是把a进行加1操作。后面的调用是:打印一次,进行加1,再打印。这样毫无疑问,输出结果是1和2了。为什么闭包callback2会和callback1共用一个UpValue呢?
是这样的,在第二个闭包创建的时候,它会在调用luaF_findupval时去openvalue的链表里查找是否有复用的UpValue,如果有,就复用它,否则会创建一个新的UpValue。
UpVal *luaF_findupval (lua_State *L, StkId level) {
global_State *g = G(L);
GCObject **pp = &L->openupval;
UpVal *p;
UpVal *uv;
while (*pp != NULL && (p = gco2uv(*pp))->v >= level) {/* 在openvalue链表里查找是否有合适的UpValue */
GCObject *o = obj2gco(p);
lua_assert(p->v != &p->u.value);
if (p->v == level) { /* found a corresponding upvalue? */
if (isdead(g, o)) /* is it dead? */
changewhite(o); /* resurrect it */
return p;
}
resetoldbit(o); /* may create a newer upval after this one */
pp = &p->next;
}
/* 如果找不到,就创建一个新的 */
uv = &luaC_newobj(L, LUA_TUPVAL, sizeof(UpVal), pp, 0)->uv;
uv->v = level; /* current value lives in the stack */
uv->u.l.prev = &g->uvhead; /* double link it in `uvhead' list */
uv->u.l.next = g->uvhead.u.l.next;
uv->u.l.next->u.l.prev = uv;
g->uvhead.u.l.next = uv;
lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
return uv;
}
这样的话就能达到复用的情况,callback0和callback1的UpValue a都指向了同一个地址。此时的内存图为:
|
请发表评论