之前读书的时候,比较喜欢玩python,对于lua,就知道专门用来做游戏脚本的,一直没机会接触到。有幸的是,今年进入了一个新项目,用到了lua.我做事不喜欢不明不白,所以在使用lua过程中遇到不明一般都喜欢看源码探其原由。
lua51和lua52都有一个叫全局表_G,遍历这个表就可以知道,这个表保存了lua所有的全局函数和全局变量。
而到了lua52,有个叫_ENV的玩意,遍历这个表也知道,也得到跟_G一样的结果.
先看看这个_G是如何产生的。
在lua中,多个lua state会共享一个global state,在通过luaL_newstate()产生lua state而后初始化的过程中,在f_luaopen这个函数会调用init_registry初始化一个叫注册表的玩意.
如下,调用栈:
test.exe!init_registry(lua_State * L, global_State * g) 行 168 C
test.exe!f_luaopen(lua_State * L, void * ud) 行 187 + 0xd 字节 C
test.exe!luaD_rawrunprotected(lua_State * L, void (lua_State *, void *)* f, void * ud) 行 133 + 0x1f 字节 C
test.exe!lua_newstate(void * (void *, void *, unsigned int, unsigned int)* f, void * ud) 行 304 + 0x10 字节 C
test.exe!luaL_newstate() 行 938 + 0xc 字节 C
test.exe!main() 行 26 + 0x5 字节 C++
init_registry源码:
static void init_registry (lua_State *L, global_State *g) {
TValue mt;
/* create registry */
Table *registry = luaH_new(L);
sethvalue(L, &g->l_registry, registry);
luaH_resize(L, registry, LUA_RIDX_LAST, 0);
/* registry[LUA_RIDX_MAINTHREAD] = L */
setthvalue(L, &mt, L);
luaH_setint(L, registry, LUA_RIDX_MAINTHREAD, &mt);
/* registry[LUA_RIDX_GLOBALS] = table of globals */
sethvalue(L, &mt, luaH_new(L));
luaH_setint(L, registry, LUA_RIDX_GLOBALS, &mt);
}
看源码的时候,没必面一开始就每一个函数,每一个宏都要搞清楚,通过其函数名大概知道其用意就行了,等对整体有了解再慢慢细化。
这个函数中,先产生一个lua的table,叫注册表,然后初始化其数组部分的大小为LUA_RIDX_LAST(2)(lua的table有数组跟hash两部分),然后把数组部分的LUA_RIDX_MAINTHREAD(1)赋值为L,也就是把这个注册表的数组部分的第一个元素赋值为主线程的状态机L(这里所说的线程并非是os的线程,而是lua的状态机概念)。通过luaL_newstate产生的lua state都会同时创建一个global state,很自然的就是,这个lua state就是主线程。
接下来,luaH_new创建一个table,再通过luaH_setint(L, registry, LUA_RIDX_GLOBALS, &mt)把刚创建的空table赋到LUA_RIDX_GLOBALS(2),也就是注册表的数组2指向的是一个全局表。
接下来看luaopen_base这个函数.
LUAMOD_API int luaopen_base (lua_State *L) {
/* set global _G */
lua_pushglobaltable(L);
lua_pushglobaltable(L);
lua_setfield(L, -2, "_G");
/* open lib into global table */
luaL_setfuncs(L, base_funcs, 0);
lua_pushliteral(L, LUA_VERSION);
lua_setfield(L, -2, "_VERSION"); /* set global _VERSION */
return 1;
}
创建一个key为_G,指向注册表自已。然后通过luaL_setfuncs把基础的函数base_funcs注册到注册表中。
于是注册表就成了这样子。。
再看看_ENV是啥玩意,看lua_load函数是啥玩意:
LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data,
const char *chunkname, const char *mode) {
ZIO z;
int status;
lua_lock(L);
if (!chunkname) chunkname = "?";
luaZ_init(L, &z, reader, data);
status = luaD_protectedparser(L, &z, chunkname, mode);
if (status == LUA_OK) { /* no errors? */
LClosure *f = clLvalue(L->top - 1); /* get newly created function */
if (f->nupvalues == 1) { /* does it have one upvalue? */
/* get global table from registry */
Table *reg = hvalue(&G(L)->l_registry);
const TValue *gt = luaH_getint(reg, LUA_RIDX_GLOBALS);
/* set global table as 1st upvalue of 'f' (may be LUA_ENV) */
setobj(L, f->upvals[0]->v, gt);
luaC_barrier(L, f->upvals[0], gt);
}
}
lua_unlock(L);
return status;
}
parser完后并没有错误会看到,会创建一个LClosure,lua的闭包,也就是每个lua文件load完后是一个lua闭包,是闭包的话自然就会有upvalue,所以这个闭包的第一个upvalue 就是regitry[LUA_RIDX_GLOBALS] 。
也就是注册表registry[2] = globaltable,_ENV = globaltable,globaltable["_G"] = globaltable.
本文探讨了Lua中的全局表_G和_ENV,指出两者在lua52中具有相同的效果,展示了_LUA_RIDX_GLOBALS如何在注册表中初始化全局表,并详细解释了luaopen_base函数如何将基础函数注册到_G。同时,文章揭示了_ENV的设置过程,通过lua_load函数关联注册表、全局表与_ENV的关系。

4111

被折叠的 条评论
为什么被折叠?



