lua闭包Upvalue

闭包

lua任何函数都是闭包,闭包至少带1个upValue;

CClosure是使用Lua提供的lua_pushcclosure这个C-Api加入到虚拟栈中的C函数,它是对LClosure的一种C模拟

如string.gmatch就是cclosure

定义:

c 复制代码
#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;

上值

lua 复制代码
local a = 1
local b = 2
function xxx()
    local c = 3
    local d = 4
    print(a + b)
    function yyy()
        print(c + d)
    end
end

我们可以看到,每个lua函数,都有一个upvalue列表,并且他们首个upvalue,都是一个名为_ENV的upvalue,内层lua函数的_ENV指向外层lua函数的_ENV,而最外层的top-level函数,则将值指向了全局表_G。为什么lua要用这种组织方式?将_ENV作为每个lua函数的第0个upvalue呢?我认为,这是为了效率,同时也能是的逻辑更为清晰,lua函数去查找一个变量的方式,如下所示:

lua函数查找一个变量v,首先会在自己的local变量中查找,如果找到就直接获取它的值,找不到则进入下一步

查找upvalue列表,有没有一个名为v的upvalue,有则获取它的值,没有则进入下一步

到_ENV里去查找一个名为v的值

上值的确定(编译期)

用来表示upvalue的有两个数据结构,一个是编译时期,存储upvalue信息的Upvaldesc (这个结构并不存储upvalue的实际值,只是用来标记upvalue的位置信息),还有一个是在运行期,实际存储upvalue值的UpVal结构。它们的结构定义如下所示:

c 复制代码
typedef struct upvaldesc {
  lu_byte in_stack;
  lu_byte idx;
  TString* name;
} upvaldesc;

typedef struct  UpVal {
  CommonHeader;
  TValue *v;  /* points to stack or to its own value */
  union {
	// 当这个upval被close时,保存upval的值,后面可能还会被引用到
    TValue value;  /* the value (when closed) */
    // 当这个upval还在open状态时,以下链表串连在openupval链表中
    struct {  /* double linked list (when open) */
      struct UpVal *prev;
      struct UpVal *next;
    } l;
  } u;
} UpVal;

举例:

lua 复制代码
local a = 1
local b = 2
function xxx()
    local c = 3
    print(a)
    function yyy()
        print(a+b+c)
    end
end

编译期确定upvalue,最终的结构如下:

LClosure实际是运行时才会实例化的,这里为了展示方便;

分成3个level, top level, level2, level3;
top level : Upvaldesc列表只有1个,指代_ENV
level2 : Upvaldesc列表有3个,第一个是_ENV, 是上一级的上值列表的第一个(所以in_stack=0,idx=0); 第二个是变量a, 是上一级的局部变量在栈上第一个(所以in_stack=1,idx=0);第三个是变量b, 是上一级的局部变量在栈上第二个(所以in_stack=1,idx=1)
level3: Upvaldesc列表有4个,第一个是_ENV, 是上一级的上值列表的第一个(所以in_stack=0,idx=0); 第二个是变量a, 是上一级的上值列表的第二个(所以in_stack=0,idx=1);第三个是变量b, 是上一级的上值列表的第三个(所以in_stack=0,idx=2);第四个是变量c,是上一级局部变量在栈上第一个(所以in_stack=1,idx=0)

上值生成(运行时)

当加载这段代码块时,level2的upval列表会加入2个UpVal,指向栈(变量a和b);level3的UpVal不会生成因为没有调用xxx

当调用xxx时,最终生成的upval列表如下

上值的open和close

如下图的代码段,当调用aaa()时,var1和var2都在栈上,处于open 状态,UpVal结构的v指针指向栈上数据

但是aaa调用完毕,var2会被回收,弹出栈,如下:

在aaa函数执行完毕时,他们的UpVal实例,会进行close操作,什么意思呢,就是原来的upval->v指向栈的某个位置,现在这个关联将被破除,并且upval->v的值,赋值为upval->u.value的地址,同时,upval->v原来指向的值,会被赋值到upval->u.value上(绿色框2个UpVal指向了新的空间)

闭包共享/不共享上值

f1和f2分别创建了一个闭包实例,var1是top level的局部变量,能共享;var2则是独立上值,且在aaa()调用完毕后会从栈上移除,转为close状态(可以看到var2的UpVal结构,v指针不是指向栈,而是union中的TValue结构)

相关推荐
姓蔡小朋友1 天前
LUA脚本
开发语言·junit·lua
金融新世界2 天前
推动产业升级:倒逼转型创新与重构产业链格局
lua
oMcLin2 天前
如何在 Debian 11 上配置并优化 Nginx 与 Lua 脚本,提升高并发网站的动态请求处理能力?
nginx·debian·lua
源代码•宸2 天前
goframe框架签到系统项目开发(补签逻辑实现、编写Lua脚本实现断签提醒功能、简历示例)
数据库·后端·中间件·go·lua·跨域·refreshtoken
每天回答3个问题2 天前
Lua 函数教程
开发语言·ue5·ue4·lua
星空露珠3 天前
时间罗盘小界面模组
开发语言·数据结构·算法·游戏·lua
fredricen3 天前
Openwrt21.02应用日志全局开关控制
lua·openwrt·luci
每天回答3个问题3 天前
Lua Table(表)
开发语言·ue4·lua·虚幻引擎
Kang.Charles3 天前
Lua创建Class
开发语言·lua
星空露珠3 天前
2048小游戏制作程序
开发语言·数据库·算法·游戏·lua