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

转载 2016年08月28日 14:49:57

转自:http://www.tuicool.com/articles/RVZvMbn

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

function treaverse_global_env(curtable,level)
      for key,value in pairs(curtable or {}) do
    local prefix = string.rep(" ",level*5)
    print(string.format("%s%s(%s)",prefix,key,type(value)))

    --注意死循环
    if (type(value) == "table" ) and key ~= "_G" and (not value.package) then
        treaverse_global_env(value,level + 1)
    elseif (type(value) == "table" ) and (value.package) then
        print(string.format("%sSKIPTABLE:%s",prefix,key))
    end 
      end 
  end

  treaverse_global_env(_G,0)
注意Lua虚拟机本身是不会使用_G这个变量的,在脚本中,可以任意改变这个变量_G的值,不会影响任何环境或副作用。比如下面代码: 
local cf = loadstring(" local i=0  i=i+1 print(i) ")

  --从后面两个输出我们可以看出,生成的函数的环境就是全局_G
  print(cf,getfenv(cf),_G)  -- function: 0025AF58      table: 00751C68 table: 00751C68

  --改变_G的值
  _G = {}
  cf()  --1

  --虽然改变了_G的值,但函数的的环境仍然是全局环境table地址仍然是00751C68
  print(cf,getfenv(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代码如下:
/*
  ** 创建注册表和表中预定义的值
  */
  static void init_registry (lua_State *L, global_State *g) {
    TValue mt; 
    /*创建注册表,初始化注册表数组部分大小为LUA_RIDX_LAST*/
    Table *registry = luaH_new(L);
    sethvalue(L, &g->l_registry, registry);
    luaH_resize(L, registry, LUA_RIDX_LAST, 0); 

    /*把这个注册表的数组部分的第一个元素赋值为主线程的状态机L(这里所说的线程并非是os的线程,而是lua的状态机概念)*/
    /*即 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);
通过init_registry函数的实现可以看出,在创建注册表的同时,创建了全局环境,并把这个全局表赋值给注册表数组部分的第二个元素。通过下面代码,把上面创建的全局表注册到脚本中的,这样在脚本就可以使用变量_G来获取全局表了,代码如下: 
static const luaL_Reg loadedlibs[] = { 
    {"_G", luaopen_base},
    {LUA_LOADLIBNAME, luaopen_package},
    /*
    **省略了一些代码
  */
    {LUA_DBLIBNAME, luaopen_debug},
    {NULL, NULL}
  };

  LUALIB_API void luaL_openlibs (lua_State *L) {
    const luaL_Reg *lib;
    /* 从'loadedlibs'中调用函数,并把调用的结果res除了package.loaded[modname]=res */
    /*同时设置到全局变量modname中,供脚本层调用*/
    for (lib = loadedlibs; lib->func; lib++) {
      luaL_requiref(L, lib->name, lib->func, 1); 
      lua_pop(L, 1);  /* remove lib */
    }
    /*
    **省略了一些代码
  */
  }
在函数luaopen_base中会把脚本用到的函数注册到全局表, 代码如下: 
static const luaL_Reg base_funcs[] = { 
    {"assert", luaB_assert},
    {"collectgarbage", luaB_collectgarbage},
    /*
    **省略了一些代码
  */
    {"xpcall", luaB_xpcall},
    {NULL, NULL}
  };
    
  LUAMOD_API int luaopen_base (lua_State *L) {
    /* 设置_G._G = _G*/
    lua_pushglobaltable(L);
    lua_pushglobaltable(L);
    lua_setfield(L, -2, "_G");
    /*在全局表中添加脚本中用到的全局函数*/
    luaL_setfuncs(L, base_funcs, 0); 
    lua_pushliteral(L, LUA_VERSION);
    lua_setfield(L, -2, "_VERSION");  /* set global _VERSION */
    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 

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

在Lua脚本层,Lua将所有的全局变量保存在一个常规的table中,这个table被称为全局环境,并且将这个table保存在一个全局变量_G中,文章首先通过一个递归函数,打印出_G中所有的信息,然后说...
  • MaximusZhou
  • MaximusZhou
  • 2014年04月19日 13:53
  • 8115

5.1之前, 全局变量存储在_G这个table中, 这样的操作:5.3 lua_setupvalue

5.1之前, 全局变量存储在_G这个table中, 这样的操作: a = 1  相当于: _G['a'] = 1 但在5.2之后, 引入了_ENV叫做环境,与_G全局变量表产生了一些混淆,...
  • linuxheik
  • linuxheik
  • 2016年12月06日 23:11
  • 723

lua全局环境变量_G小结

1.以一个模块(*.lua,后同)为单位,所有的全局变量都保存在table中,这个table放在_G中,这是一个全局空间(全局环境变量),可以简单的看作一个表。 2._G中只保存全局变量,不保存局部变...
  • peter_teng
  • peter_teng
  • 2016年10月07日 21:43
  • 1317

遍历Lua全局环境变量

Lua全局变量 Lua解释器提供了很多全局变量,比如print等,便于程序开发。Lua提供的所有全局变量都保存在一个普通的表_G中。目前Lua-5.2.1中_G中的全局变量主要有“字符串”、“函数”...
  • luyafei_89430
  • luyafei_89430
  • 2014年11月04日 13:22
  • 3091

【lua全局变量_G】

1.全局变量与环境 lua中真正存储全局变量的地方不是在_G里面,而是在setfenv(i,table)的table中,所有当前的全局变量都在这里面找,只不过在程序开始时lua会默认先设置一个变...
  • yanxin007
  • yanxin007
  • 2014年11月14日 14:33
  • 790

《深入理解ES6》阅读笔记 --- 用模块封装代码

不知不觉《深入理解ES6》阅读笔记就写到了最后一篇,完结之后可能会开启另外的一个系列,分享自己的知识点,让阅读到的人有一点点的收获,以及自己的成长。最后一篇主要是来写一写用模块封装代码的事情,回顾历史...
  • sreddouilyongxia
  • sreddouilyongxia
  • 2017年10月10日 09:47
  • 72

lua防止访问不存在的全局变量

当访问lua中不存在的全局变量时并不会报错,而是返回nil值。在我们开发的时候稍微不注意写错了变量名,程序并不会报错,类似于下面这种情况。 test = 1 print(tets) 为了防止这种情...
  • wxc237786026
  • wxc237786026
  • 2016年06月15日 22:54
  • 949

lua5.2 带你理解_ENV和_G

5.1之前, 全局变量存储在_G这个table中, 这样的操作: a = 1  相当于: _G['a'] = 1 但在5.2之后, 引入了_ENV叫做环境,与_G全局变量表产生了一些混淆,...
  • liutianshx2012
  • liutianshx2012
  • 2014年12月25日 13:39
  • 569

c获取lua全局变量 5

lua文件内容 width = 500; height = 300; c代码如下 lua_State* IniLua() { lua_State* L = luaL_newstate(); l...
  • u012607841
  • u012607841
  • 2014年03月23日 14:12
  • 816

python 中的赋值、引用、拷贝、作用域

http://my.oschina.net/leejun2005/blog/145911 目录[-] 1、先来看个问题吧: 2、引用 VS 拷贝: 3、增强赋值以及共享引用: 4、python...
  • qinglu000
  • qinglu000
  • 2016年05月05日 14:21
  • 408
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深入理解Lua的全局变量_G以及源码实现
举报原因:
原因补充:

(最多只允许输入30个字)