Lua 源码分析之闭包Closure

        闭包是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都指向了同一个地址。此时的内存图为:

发布了70 篇原创文章 · 获赞 48 · 访问量 19万+

猜你喜欢

转载自blog.csdn.net/fwb330198372/article/details/89217131