在 lua 中函数是作为第一类值存在的,即它和表、字符串、数值一样可以被变量引用。函数有三种类型 lua 函数 (LUA_TLCL)、轻量级 C 函数 (LUA_TLCF)、C 函数 (LUA_TCCL)。其中 LUA_TLCL 和 LUA_TCCL 属于 GCUnion–需要被回收的类型,被称为 Closure。当 LUA_TCCL 不包含 upvalue 时,直接用 C 函数指针即可,不必构造 Closure 对象, 也就是 LUA_TLCF。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | /* lua.h */ /* Type for C functions registered with Lua */ typedef int (*lua_CFunction) (lua_State *L); /* lobject.h */ /* Variant tags for functions */ /* Closures */ typedef struct CClosure { GCObject *next; lu_byte tt; lu_byte marked; lu_byte nupvalues; /* nums of upvalue */ GCObject *gclist; lua_CFunction f; TValue upvalue[1]; /* list of upvalues */ } CClosure; typedef struct LClosure { GCObject *next; lu_byte tt; lu_byte marked; lu_byte nupvalues; /* nums of upvalue */ GCObject *gclist; struct Proto *p; UpVal *upvals[1]; /* list of upvalues */ } LClosure; typedef union Closure { CClosure c; LClosure l; } Closure; |
lua 的闭包分成两类:CClosure 是 C 的函数闭包。LClosure 是 Lua 的函数闭包。
LClosure
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | /* lobject.h */ /* Function Prototypes */ typedef struct Proto { GCObject *next; lu_byte tt; lu_byte marked; lu_byte numparams; /* number of fixed parameters */ lu_byte is_vararg; /* 2: declared vararg; 1: uses vararg */ lu_byte maxstacksize; /* number of registers needed by this function */ int sizeupvalues; /* size of 'upvalues' */ int sizek; /* size of 'k' */ int sizecode; int sizelineinfo; int sizep; /* size of 'p' */ int sizelocvars; int linedefined; /* debug information */ int lastlinedefined; /* debug information */ TValue *k; /* constants used by the function */ Instruction *code; /* opcodes */ struct Proto **p; /* functions defined inside the function */ int *lineinfo; /* map from opcodes to source lines (debug information) */ LocVar *locvars; /* information about local variables (debug information) */ Upvaldesc *upvalues; /* upvalue information */ struct LClosure *cache; /* last-created closure with this prototype */ TString *source; /* used for debug information */ GCObject *gclist; } Proto; /* lfunc.h */ /* Upvalues for Lua closures */ struct UpVal { TValue *v; /* points to stack or to its own value */ lu_mem refcount; /* reference counter */ union { struct { /* (when open) */ UpVal *next; /* linked list */ int touched; /* mark to avoid cycles with dead threads */ } open; TValue value; /* (when closed) the value */ } u; }; |
LClosure 由 Proto 和 UpVal 组成。Proto 描述了 lua 函数的函数原型,记录了函数原型的字节码、函数引用的常量表、调试信息、参数、栈大小等信息。UpVal 保存了对 upvalue 的引用。它直接用一个 TValue 指针引用一个 upvalue 值变量。当被引用的变量还在数据栈上时,这个指针直接指向栈上的 TValue,那么这个 upvalue 被称为开放的。由于 lua 的数据栈的大小可扩展,当数据栈内存延展时 (realloc),其内存地址会发生变化,这时需要将 upvalue 拷贝到 UpVal.u.value 中保存。
CClosure
CClosure 由 lua_CFunction 和 TValue 组成。C 函数可以用闭包的方式嵌入 lua,与 LClosure 相比 CClosure 天生就是关闭的。因此,直接使用 TValue 来保存 upvalue。而 lua 与 C 交互的函数原型统一使用 lua_CFunction。当某个函数没有 upvalue 时,则不需要创建闭包直接使用 lua_CFunction。也称为 light C function。