Skip to content

Latest commit

 

History

History
168 lines (140 loc) · 7.39 KB

ch06-环境相关的变量.md

File metadata and controls

168 lines (140 loc) · 7.39 KB

##环境相关的变量 这里首先分析几个特殊的与环境相关的变量:全局表,分别是Global表,env表,registry表以及UpValue. 需要注意的是,这几个表中:

  1. Global表是存放在lua_State结构体中的,也就是每个lua_State结构体都有一个对应的global表,不用多说,就是存放全局变量之用;
  2. env表是存放在Closure结构体中的,也就是每个函数有自己独立的一个环境;
  3. 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 }

这里的设计其实很巧妙,仅使用一个数组就模拟了一个链表的实现.其原理在于:

  1. FREELIST_REF做为registry表中存放数字key freelist的key,每次需要存储之前,都会先到这里拿到当前存放的值.
  2. 如果拿出来的值是0,说明当前的freelist中还没有数据,直接返回当前registry表的数据量做为新的索引.
  3. 当调用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的几种情况,分别返回不同的值:

  1. 如果idx>0,那么以idx值为索引,返回基于Lua_State的base指针的值,也就是相对于栈底向上的偏移值;
  2. 如果idx>LUA_REGISTRYINDEX,则以idx值为索引,返回基于Lua_State的top指针的值,也就是相对于栈顶向下的偏移值;
  3. 如果是LUA_REGISTRYINDEX,那么返回registry表;
  4. 如果是LUA_ENVIRONINDEX,返回当前函数的env表;
  5. 如果是LUA_GLOBALSINDEX,返回Global表;
  6. 如果以上都不符合,那么将根据情况返回当前函数的UpValue数组中的值.