##环境相关的变量 这里首先分析几个特殊的与环境相关的变量:全局表,分别是Global表,env表,registry表以及UpValue. 需要注意的是,这几个表中:
- Global表是存放在lua_State结构体中的,也就是每个lua_State结构体都有一个对应的global表,不用多说,就是存放全局变量之用;
- env表是存放在Closure结构体中的,也就是每个函数有自己独立的一个环境;
- registry表是存放在global_State结构体中的,这个结构体整个运行环境中只有一份,换言之, registry表是全局唯一的.
这几个表的作用分别是什么呢?
前面在讲解到OP_GETGLOBAL以及OP_SETGLOBAL指令时,说到获取全局变量,其实更精确的说,并不是全局变量,而是在当前函数的env中查找得到的:
(lvm.c)
428 case OP_GETGLOBAL: {
429 TValue g;
430 TValue *rb = KBx(i);
431 sethvalue(L, &g, cl->env);
432 lua_assert(ttisstring(rb));
433 Protect(luaV_gettable(L, &g, rb, ra));
434 continue;
435 }
440 case OP_SETGLOBAL: {
441 TValue g;
442 sethvalue(L, &g, cl->env);
443 lua_assert(ttisstring(KBx(i)));
444 Protect(luaV_settable(L, &g, KBx(i), ra));
445 continue;
446 }
可以看到,这两个操作,都是到函数对应的Closure指针中的env表中去查询数据.因此,如果执行以下的Lua代码:
setfenv(1,{})
print(a)
实际上会查找不到Lua标准库提供的print函数,提示报错"attempt to call global 'print' (a nil value)",原因就是首先使用setfenv函数将当前函数的env表置为了一个空表,此时在当前函数的env表中查找不到这个名字的函数了.
来看函数的env表是如何创建的.在创建一个Closure对象时,都会调用getcurrenv函数来获取当前环境表:
(lapi.c)
79 static Table *getcurrenv (lua_State *L) {
80 if (L->ci == L->base_ci) /* no enclosing function? */
81 return hvalue(gt(L)); /* use global table as environment */
82 else {
83 Closure *func = curr_func(L);
84 return func->c.env;
85 }
86 }
它将区分两种情况,如果当前没有在任何一个函数中,那么直接返回Global表,否则就返回当前函数的env表,换言之,环境表会逐层继承.
接着来看registry表的作用.registry表存放在global_State结构体中,因此里面的内容可供多个lua_State访问,另外这个表只能由C代码进行访问,Lua代码不能访问,除此之外和普通的表没有什么区别.但是需要注意的是,使用普通的对表进行赋值的API对registry表进行赋值时,应该使用的是字符串类型的key,Lua API中对外提供了接口lua_ref/lua_unref/lua_getref宏,用于提供在registry表中存取唯一的数字key的数据,通过这组API,使用者不需要关心给某个需要存放到registry的数据分配一个全局唯一的key,由Lua解释器自己来保证这一点:
(lauxlib.h)
162 #define lua_ref(L,lock) ((lock) ? luaL_ref(L, LUA_REGISTRYINDEX) : \
163 (lua_pushstring(L, "unlocked references are obsolete"), lua_error(L), 0))
164
165 #define lua_unref(L,ref) luaL_unref(L, LUA_REGISTRYINDEX, (ref))
166
167 #define lua_getref(L,ref) lua_rawgeti(L, LUA_REGISTRYINDEX, (ref))
来看看这里面的luaL_ref/luaL_unref函数的实现,需要说明的是,在调用luaL_ref函数之前,需要存放的数据已经位于栈顶:
(lauxlib.c)
481 LUALIB_API int luaL_ref (lua_State *L, int t) {
482 int ref;
483 t = abs_index(L, t);
484 if (lua_isnil(L, -1)) {
485 lua_pop(L, 1); /* remove from stack */
486 return LUA_REFNIL; /* `nil' has a unique fixed reference */
487 }
488 lua_rawgeti(L, t, FREELIST_REF); /* get first free element */
489 ref = (int)lua_tointeger(L, -1); /* ref = t[FREELIST_REF] */
490 lua_pop(L, 1); /* remove it from stack */
491 if (ref != 0) { /* any free element? */
492 lua_rawgeti(L, t, ref); /* remove it from list */
493 lua_rawseti(L, t, FREELIST_REF); /* (t[FREELIST_REF] = t[ref]) */
494 }
495 else { /* no free elements */
496 ref = (int)lua_objlen(L, t);
497 ref++; /* create new reference */
498 }
499 lua_rawseti(L, t, ref);
500 return ref;
501 }
502
503
504 LUALIB_API void luaL_unref (lua_State *L, int t, int ref) {
505 if (ref >= 0) {
506 t = abs_index(L, t);
507 lua_rawgeti(L, t, FREELIST_REF);
508 lua_rawseti(L, t, ref); /* t[ref] = t[FREELIST_REF] */
509 lua_pushinteger(L, ref);
510 lua_rawseti(L, t, FREELIST_REF); /* t[FREELIST_REF] = ref */
511 }
512 }
这里的设计其实很巧妙,仅使用一个数组就模拟了一个链表的实现.其原理在于:
- FREELIST_REF做为registry表中存放数字key freelist的key,每次需要存储之前,都会先到这里拿到当前存放的值.
- 如果拿出来的值是0,说明当前的freelist中还没有数据,直接返回当前registry表的数据量做为新的索引.
- 当调用luaL_unref释放一个索引值的时候,同样是首先将这个位置的数据存放为FREELIST_REF的值,再将这个返回的索引保存到FREELIST_REF中,这样就能始终保存在FREELIST_REF一直都是存放的可用索引的值.
最后来看UpValue,前面谈到的几个全局变量,registry表提供的是全局变量的存储,环境表提供的是模块内全局变量的存储,UpValue用于提供函数内静态变量的存储,这些变量存储的地方,倒不是某个特殊的表,其实就是换算成对应的UpValue的索引值来访问函数的UpValue数组而已.
看完了前面的分析,来看一个关键的函数index2adr,这个函数中集成了所有不论是访问栈上元素的索引,还是前面这几种特殊变量的索引,都转换为对应的地址:
(lua.h)
36 #define LUA_REGISTRYINDEX (-10000)
37 #define LUA_ENVIRONINDEX (-10001)
38 #define LUA_GLOBALSINDEX (-10002)
39 #define lua_upvalueindex(i) (LUA_GLOBALSINDEX-(i))
(lapi.c)
49 static TValue *index2adr (lua_State *L, int idx) {
50 if (idx > 0) {
51 TValue *o = L->base + (idx - 1);
52 api_check(L, idx <= L->ci->top - L->base);
53 if (o >= L->top) return cast(TValue *, luaO_nilobject);
54 else return o;
55 }
56 else if (idx > LUA_REGISTRYINDEX) {
57 api_check(L, idx != 0 && -idx <= L->top - L->base);
58 return L->top + idx;
59 }
60 else switch (idx) { /* pseudo-indices */
61 case LUA_REGISTRYINDEX: return registry(L);
62 case LUA_ENVIRONINDEX: {
63 Closure *func = curr_func(L);
64 sethvalue(L, &L->env, func->c.env);
65 return &L->env;
66 }
67 case LUA_GLOBALSINDEX: return gt(L);
68 default: {
69 Closure *func = curr_func(L);
70 idx = LUA_GLOBALSINDEX - idx;
71 return (idx <= func->c.nupvalues)
72 ? &func->c.upvalue[idx-1]
73 : cast(TValue *, luaO_nilobject);
74 }
75 }
76 }
这段代码的逻辑,主要是根据传入的idx的几种情况,分别返回不同的值:
- 如果idx>0,那么以idx值为索引,返回基于Lua_State的base指针的值,也就是相对于栈底向上的偏移值;
- 如果idx>LUA_REGISTRYINDEX,则以idx值为索引,返回基于Lua_State的top指针的值,也就是相对于栈顶向下的偏移值;
- 如果是LUA_REGISTRYINDEX,那么返回registry表;
- 如果是LUA_ENVIRONINDEX,返回当前函数的env表;
- 如果是LUA_GLOBALSINDEX,返回Global表;
- 如果以上都不符合,那么将根据情况返回当前函数的UpValue数组中的值.