微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

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

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

<span style="font-size:18px;">	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",key))
		end 
	    end 
	end

	treaverse_global_env(_G,0)</span>
注意Lua虚拟机本身是不会使用_G这个变量的,在脚本中,可以任意改变这个变量_G的值,不会影响任何环境或副作用。比如下面代码

<span style="font-size:18px;">	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,_G)  -- function: 0075AF58      table: 00751C68 table: 0075B468</span>
认情况下,在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用下面宏定义的:

<span style="font-size:18px;">	#define lua_pushglobaltable(L) lua_rawgeti(L,LUA_REGISTRYINDEX,LUA_RIDX_GLOBALS)</span>
这里的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代码如下:

<span style="font-size:18px;">	/*
	** 创建注册表和表中预定义的值
	*/
	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,LUA_RIDX_MAINTHREAD,&mt);

	  /*把注册表的数组部分的第二个元素赋值为全局表,即registry[LUA_RIDX_GLOBALS] = table of globals */
	  sethvalue(L,luaH_new(L));
	  luaH_setint(L,LUA_RIDX_GLOBALS,&mt);
	</span>
通过init_registry函数的实现可以看出,在创建注册表的同时,创建了全局环境,并把这个全局表赋值给注册表数组部分的第二个元素。通过下面代码,把上面创建的全局表注册到脚本中的,这样在脚本就可以使用变量_G来获取全局表了,代码如下:
<span style="font-size:18px;">	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 */
	  }
	  /*
	  **省略了一些代码
          */
	}</span>
函数luaopen_base中会把脚本用到的函数注册到全局表, 代码如下:

<span style="font-size:18px;">	static const luaL_Reg base_funcs[] = { 
	  {"assert",luaB_assert},{"collectgarbage",luaB_collectgarbage},/*
	  **省略了一些代码
          */
	  {"xpcall",luaB_xpcall},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,"_VERSION");  /* set global _VERSION */
	  return 1; 
	</span>


参考资料:

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐