深入理解Lua的全局变量_G以及源码实现

在Lua脚本层,Lua将所有的全局变量保存在一个常规的table中,这个table被称为全局环境,并且将这个table保存在一个全局变量_G中,也就是说在脚本中可以用_G获取这个全局table,并且有_G._G == _G,在默认情况,Lua在全局环境_G中添加了标准库比如math、函数比如pairs等。可以通过下面代码,可以递归打印_G中的所有信息:

[cpp]  view plain copy

在CODE上查看代码片

派生到我的代码片

  1. function treaverse_global_env(curtable,level)  
  2.     for key,value in pairs(curtable or {}) do  
  3.     local prefix = string.rep(" ",level*5)  
  4.     print(string.format("%s%s(%s)",prefix,key,type(value)))  
  5.   
  6.     --注意死循环  
  7.     if (type(value) == "table" ) and key ~= "_G" and (not value.package) then  
  8.         treaverse_global_env(value,level + 1)  
  9.     elseif (type(value) == "table" ) and (value.package) then  
  10.         print(string.format("%sSKIPTABLE:%s",key))  
  11.     end   
  12.     end   
  13. end  
  14.   
  15. treaverse_global_env(_G,0)  
注意Lua虚拟机本身是不会使用_G这个变量的,在脚本中,可以任意改变这个变量_G的值,不会影响任何环境或副作用。比如下面代码:

派生到我的代码片

    local cf = loadstring(" local i=0  i=i+1 print(i) ")  
  1. --从后面两个输出我们可以看出,生成的函数的环境就是全局_G  
  2. print(cf,getfenv(cf),_G)  -- function: 0025AF58      table: 00751C68 table: 00751C68  
  3. --改变_G的值  
  4. _G = {}  
  5. cf()  --1  
  6. --虽然改变了_G的值,但函数的的环境仍然是全局环境table地址仍然是00751C68  
  7. print(cf,_G)  -- function: 0075AF58      table: 00751C68 table: 0075B468  
默认情况下,在Lua中当compiles a chunk时,都是以_G作为环境的,当然可以通过函数load或loadfile,改变compiles a chunk时的环境。在C中,可以使用lua_load(类似有luaL_load*作为前缀的辅助函数)
来load a lua chunk,load的后得到的函数,默认情况下,它的第一个upvalue就是_G,我们可以改变第一个upvalue,来改变得到的函数执行环境。

变量_G是在C中注册的(源码linit.c,lbaselib.c中),在C中,可以直接调用lua_pushglobaltable把这个全局环境压入栈中,在lua5.2 该函数实质就是从注册表中获取这个全局环境,即lua_pushglobaltable用下面宏定义的:

派生到我的代码片

    #define lua_pushglobaltable(L) lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)  
这里的LUA_REGISTRYINDEX是Lua注册表(注册表是lua虚拟机范围内是全局唯一的)的伪索引,LUA_RIDX_GLOBALS是全局环境在注册表中的索引(也就说,全局环境_G是虚拟机范围内是全局唯一的)。
最后可以通过源码,来了解一下_G全局环境的变量的注册。在一个Lua虚拟机中,用一个全局结构global_State来管理多个lua_State。在调用luaL_newstate()时,在创建一个全局结构global_State和一个lua_State后,
luaL_newstate会调用f_luaopen,然后f_luaopen调用init_registry来初始化注册表,函数init_registry代码如下:

派生到我的代码片

    /* 
  1. ** 创建注册表和表中预定义的值 
  2. */  
  3. static void init_registry (lua_State *L, global_State *g) {  
  4.   TValue mt;   
  5.   /*创建注册表,初始化注册表数组部分大小为LUA_RIDX_LAST*/  
  6.   Table *registry = luaH_new(L);  
  7.   sethvalue(L, &g->l_registry, registry);  
  8.   luaH_resize(L, registry, LUA_RIDX_LAST, 0);   
  9.   /*把这个注册表的数组部分的第一个元素赋值为主线程的状态机L(这里所说的线程并非是os的线程,而是lua的状态机概念)*/  
  10. /*即 registry[LUA_RIDX_MAINTHREAD] = L */  
  11.   setthvalue(L, &mt, L);   
  12.   luaH_setint(L, LUA_RIDX_MAINTHREAD, &mt);  
  13. /*把注册表的数组部分的第二个元素赋值为全局表,即registry[LUA_RIDX_GLOBALS] = table of globals */  
  14.   sethvalue(L, luaH_new(L));  
  15. 通过init_registry函数的实现可以看出,在创建注册表的同时,创建了全局环境,并把这个全局表赋值给注册表数组部分的第二个元素。通过下面代码,把上面创建的全局表注册到脚本中的,这样在脚本就可以使用变量_G来获取全局表了,代码如下:

    派生到我的代码片

      const luaL_Reg loadedlibs[] = {   
    1.   {"_G", luaopen_base},  
    2.   {LUA_LOADLIBNAME, luaopen_package},  
    3. /* 
    4.   **省略了一些代码 
    5.          */  
    6.   {LUA_DBLIBNAME, luaopen_debug},248)">   {NULL, NULL}  
    7. };  
    8. LUALIB_API void luaL_openlibs (lua_State *L) {  
    9.   const luaL_Reg *lib;  
    10. /* 从'loadedlibs'中调用函数,并把调用的结果res除了package.loaded[modname]=res */  
    11. /*同时设置到全局变量modname中,供脚本层调用*/  
    12.   for (lib = loadedlibs; lib->func; lib++) {  
    13.     luaL_requiref(L, lib->name, lib->func, 1);   
    14.     lua_pop(L, 1);  /* remove lib */  
    15.   }  
    16.   **省略了一些代码 
    17.          */  
    18. }  
    在函数luaopen_base中会把脚本用到的函数注册到全局表, 代码如下:

    派生到我的代码片

      const luaL_Reg base_funcs[] = {   
    1.   {"assert", luaB_assert},108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important">   {"collectgarbage", luaB_collectgarbage},  
    2.   {"xpcall", luaB_xpcall},248)">       
    3. LUAMOD_API int luaopen_base (lua_State *L) {  
    4. /* 设置_G._G = _G*/  
    5.   lua_pushglobaltable(L);  
    6.   lua_pushglobaltable(L);  
    7.   lua_setfield(L, -2, "_G");  
    8. /*在全局表中添加脚本中用到的全局函数*/  
    9.   luaL_setfuncs(L, base_funcs,248)">   lua_pushliteral(L, LUA_VERSION);  
    10. "_VERSION");  /* set global _VERSION */  
    11. return 1;   


    参考资料:

    http://blog.csdn.net/ball32109/article/details/11402727
    http://blog.codingnow.com/2011/12/lua_52_env.html

    http://www.cnblogs.com/ringofthec/archive/2010/11/09/lua_State.html


    上文来自:http://blog.csdn.net/maximuszhou/article/details/24105673?utm_source=tuicool&utm_medium=referral

    相关文章

    1.github代码实践源代码是lua脚本语言,下载th之后运行thmai...
    此文为搬运帖,原帖地址https://www.cnblogs.com/zwywilliam/...
    Rime输入法通过定义lua文件,可以实现获取当前时间日期的功能...
    localfunctiongenerate_action(params)localscale_action=cc...
    2022年1月11日13:57:45 官方:https://opm.openresty.org/官...
    在Lua中的table(表),就像c#中的HashMap(哈希表),key和...