lua _G 实现

 

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

_G = {_G,........}

[cpp]  view plain  copy
  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",prefix,key))  
  11.     end   
  12.     end   
  13. end  
  14.   
  15. treaverse_global_env(_G,0)  
注意Lua虚拟机本身是不会使用_G这个变量的,在脚本中,可以任意改变这个变量_G的值,不会影响任何环境或副作用。比如下面代码:

[cpp]  view plain  copy
  1. local cf = loadstring(" local i=0  i=i+1 print(i) ")  
  2.   
  3. --从后面两个输出我们可以看出,生成的函数的环境就是全局_G  
  4. print(cf,getfenv(cf),_G)  -- function: 0025AF58      table: 00751C68 table: 00751C68  
  5.   
  6. --改变_G的值  
  7. _G = {}  
  8. cf()  --1  
  9.   --cf中存储了_G的地址   _G 可以看做一个指针  _G = {} 相当于 将_G指向一个新表  但是cf中存储的内容并没有变
  10. --虽然改变了_G的值,但函数的的环境仍然是全局环境table地址仍然是00751C68  
  11. 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用下面宏定义的:

[cpp]  view plain  copy
  1. #define lua_pushglobaltable(L) lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)  
这里的LUA_REGISTRYINDEX是Lua注册表(注册表是lua虚拟机范围内是全局唯一的)的伪索引,LUA_RIDX_GLOBALS是全局环境在注册表中的索引(也就说,全局环境_G是虚拟机范围内是全局唯一的)。


在创建注册表 向注册表中添加_G表

最后可以通过源码,来了解一下_G全局环境的变量的注册。在一个Lua虚拟机中,用一个全局结构global_State来管理多个lua_State。在调用luaL_newstate()时,在创建一个全局结构global_State和一个lua_State后,
luaL_newstate会调用f_luaopen,然后f_luaopen调用init_registry来初始化注册表,函数init_registry代码如下:

[cpp]  view plain  copy
  1. /* 
  2. ** 创建注册表和表中预定义的值 
  3. */  
  4. static void init_registry (lua_State *L, global_State *g) {  
  5.   TValue mt;   
  6.   /*创建注册表,初始化注册表数组部分大小为LUA_RIDX_LAST*/  
  7.   Table *registry = luaH_new(L);  //创建一个空的table
  8.   sethvalue(L, &g->l_registry, registry);    
  9.   luaH_resize(L, registry, LUA_RIDX_LAST, 0);   //重构 table 的大小
  10.   
  11.   /*把这个注册表的数组部分的第一个元素赋值为主线程的状态机L(这里所说的线程并非是os的线程,而是lua的状态机概念)*/  
  12.   /*即 registry[LUA_RIDX_MAINTHREAD] = L */  
  13.   setthvalue(L, &mt, L);   
  14.   luaH_setint(L, registry, LUA_RIDX_MAINTHREAD, &mt);  
  15.   
  16.   /*把注册表的数组部分的第二个元素赋值为全局表,即registry[LUA_RIDX_GLOBALS] = table of globals */  
  17.   sethvalue(L, &mt, luaH_new(L));  
  18.   luaH_setint(L, registry, LUA_RIDX_GLOBALS, &mt);  
全局表注册到脚本中的,这样在脚本就可以使用变量_G来获取全局表了,代码如下:
[cpp]  view plain  copy
  1. static const luaL_Reg loadedlibs[] = {   
  2.   {"_G", luaopen_base},  
  3.   {LUA_LOADLIBNAME, luaopen_package},  
  4.   /* 
  5.   **省略了一些代码 
  6.          */  
  7.   {LUA_DBLIBNAME, luaopen_debug},  
  8.   {NULL, NULL}  
  9. };  
  10.   
  11. LUALIB_API void luaL_openlibs (lua_State *L) {  
  12.   const luaL_Reg *lib;  
  13.   /* 从'loadedlibs'中调用函数,并把调用的结果res除了package.loaded[modname]=res */  
  14.   /*同时设置到全局变量modname中,供脚本层调用*/  
  15.   for (lib = loadedlibs; lib->func; lib++) {  
  16.     luaL_requiref(L, lib->name, lib->func, 1);   
  17.     lua_pop(L, 1);  /* remove lib */  
  18.   }  
  19.   /* 
  20.   **省略了一些代码 
  21.          */  
  22. }  

在函数luaopen_base中会把脚本用到的函数注册到全局表, 代码如下:

[cpp]  view plain  copy
  1. static const luaL_Reg base_funcs[] = {   
  2.   {"assert", luaB_assert},  
  3.   {"collectgarbage", luaB_collectgarbage},  
  4.   /* 
  5.   **省略了一些代码 
  6.          */  
  7.   {"xpcall", luaB_xpcall},  
  8.   {NULL, NULL}  
  9. };  
  10.       
  11. LUAMOD_API int luaopen_base (lua_State *L) {  
  12.   /* 设置_G._G = _G*/  
  13.   lua_pushglobaltable(L);  
  14.   lua_pushglobaltable(L);  
  15.   lua_setfield(L, -2, "_G");  // _G._G =_G 
  16.   /*在全局表中添加脚本中用到的全局函数*/  
  17.   luaL_setfuncs(L, base_funcs, 0);   
  18.   lua_pushliteral(L, LUA_VERSION);  
  19.   lua_setfield(L, -2, "_VERSION");  /* set global _VERSION */  
  20.   return 1;   

总结:

_G为加载的每个模块的默认环境变量,第一个upvalue就是_G,_G保存再注册表中,

1.当创建注册表的时候创建_G同时向注册表中添加_G(注册表的创建是在lua_newstate时)

2.在openlib时 取出注册表中创建的_G table 将其压入lua栈中 

3.luaopen_base将一些全局函数放入_G表中




<think>我们正在分析问题:为什么 `bool LuaLibThread::is_running_in_coroutine(lua_State *L)` 函数始终返回 false(即判断不在协程内)? ### 问题分析 1. **函数目的**:该函数用于检测当前执行的代码是否在协程(非主线程)中运行。 2. **常见实现方法**: - 通过比较当前 `lua_State*` 与主线程状态机 - 利用 `lua_pushthread` 和 `lua_rawequal` 检查当前线程是否为主线程 ### 可能原因 #### 原因1:错误的主线程引用存储 - 若主线程引用未正确存储或初始化,比较时可能始终不匹配 - **检查点**:主线程引用是否在全局初始化时保存? ```cpp // 正确保存主线程示例 static int ref_main_thread = LUA_NOREF; void init(lua_State* L) { lua_pushthread(L); // 主线程入栈 ref_main_thread = luaL_ref(L, LUA_REGISTRYINDEX); // 保存到注册表 } ``` #### 原因2:协程检测逻辑错误 - 典型实现应排除主线程情况 ```cpp bool is_running_in_coroutine(lua_State* L) { // 获取主线程引用对应的值 lua_rawgeti(L, LUA_REGISTRYINDEX, ref_main_thread); lua_State* main_thread = lua_tothread(L, -1); lua_pop(L, 1); // 清理栈 // 当前状态机不等于主线程则为协程 return (L != main_thread); } ``` - **错误情况**:若 `ref_main_thread` 未正确设置(如 `LUA_NOREF`),则 `lua_rawgeti` 会推入 `nil`,导致 `main_thread` 为 `NULL`,比较结果恒为真(即总在协程中)。但问题描述是“始终判断不在协程内”,所以这里可能是另一个问题。 #### 原因3:引用键无效或未更新 - 若注册表引用键 `ref_main_thread` 被意外覆盖或未初始化,会导致获取到错误值 - 使用 `LUA_NOREF(-2)` 表示无效引用,若未初始化可能为0(但0可能是有效引用) #### 原因4:多状态机混淆 - 若存在多个独立的 `lua_State`(非协程),每个状态机都有自己的主线程,需要分别存储引用 ### 解决方案 #### 步骤1:验证主线程引用存储 添加调试输出验证引用有效性: ```cpp bool is_running_in_coroutine(lua_State* L) { lua_rawgeti(L, LUA_REGISTRYINDEX, ref_main_thread); if (lua_isnil(L, -1)) { fprintf(stderr, "主线程引用无效!ref=%d\n", ref_main_thread); lua_pop(L, 1); return false; // 或根据需求处理 } lua_State* main_thread = lua_tothread(L, -1); lua_pop(L, 1); return (L != main_thread); } ``` #### 步骤2:确保正确初始化 在创建主状态机后立即保存引用: ```cpp // 全局或静态变量 int ref_main_thread = LUA_NOREF; void init_lua() { lua_State* L = luaL_newstate(); luaL_openlibs(L); // 保存主线程 lua_pushthread(L); ref_main_thread = luaL_ref(L, LUA_REGISTRYINDEX); } ``` #### 步骤3:检查协程创建方式 - 通过 `lua_newthread` 创建的才是协程 - 错误示例:若直接使用主线程调用函数,则会被识别为不在协程中 ### 完整示例代码 ```cpp class LuaLibThread { static int ref_main_thread; // 静态存储主线程引用 public: static void init(lua_State* L) { lua_pushthread(L); ref_main_thread = luaL_ref(L, LUA_REGISTRYINDEX); } static bool is_running_in_coroutine(lua_State* L) { if (ref_main_thread == LUA_NOREF) { // 未初始化,按不在协程处理? return false; } lua_rawgeti(L, LUA_REGISTRYINDEX, ref_main_thread); bool is_main = lua_rawequal(L, -1, 1); // 比较栈顶(主线程)和第一个参数(当前线程)? lua_pop(L, 1); // 移除栈顶 // 更准确的做法:直接比较状态机指针 lua_rawgeti(L, LUA_REGISTRYINDEX, ref_main_thread); lua_State* main_thread = lua_tothread(L, -1); lua_pop(L, 1); return (main_thread && L != main_thread); } }; // 静态成员初始化 int LuaLibThread::ref_main_thread = LUA_NOREF; ``` ### 常见错误修正 1. **错误比较方法**: ```cpp // 错误:比较了同一个状态机的不同值 lua_pushthread(L); // 当前线程入栈 lua_rawgeti(L, LUA_REGISTRYINDEX, ref_main_thread); // 主线程入栈 bool equal = lua_rawequal(L, -1, -2); // 比较栈顶两个值 lua_pop(L, 2); return !equal; // 不等则是在协程 ``` - 此方法可行,但需确保栈操作正确 2. **未正确处理无效引用**: - 当 `ref_main_thread` 为 `LUA_NOREF` 时,应视为未初始化,可以返回 `false` 或报错 ### 总结原因 函数始终返回 `false`(不在协程)的原因可能是: 1. 主线程引用未正确初始化(`ref_main_thread` 保持 `LUA_NOREF`) 2. 获取主线程时得到 `nil`(引用被意外释放) 3. 当前状态机 `L` 就是主线程(逻辑正确但调用场景不符合预期) > 通过添加调试输出检查引用有效性,并确保初始化流程正确,可解决该问题[^1]。 §§相关问题§§ 1. Lua 中如何正确管理注册表引用避免内存泄漏? 2. 在多模块系统中如何安全共享主线程引用? 3. 协程和线程在 Lua 中的本质区别是什么?[^4] </think> ### LuaLibThread::is_running_in_coroutine 始终返回 false 的原因分析 当 `LuaLibThread::is_running_in_coroutine(lua_State *L)` 始终判断不在协程内时,通常是以下原因造成的: #### 1. **主线程引用未正确初始化**(最常见原因) ```cpp // 错误示例:主线程引用未保存 bool LuaLibThread::is_running_in_coroutine(lua_State *L) { lua_State* main = nullptr; // 未初始化主线程引用 return (L != main); // 永远返回 true(false 的相反情况) } ``` - **问题**:主线程引用未在全局初始化时保存 - **解决方案**: ```cpp // 正确保存主线程引用 static int ref_main = LUA_NOREF; void init(lua_State* L) { lua_pushthread(L); // 主线程入栈 ref_main = luaL_ref(L, LUA_REGISTRYINDEX); // 保存到注册表 } ``` #### 2. **协程检测逻辑错误** ```cpp // 错误示例:直接比较指针 bool is_running_in_coroutine(lua_State* L) { return L != lua_main_thread; // 错误比较 } ``` - **问题**:协程和主线程本质是同一状态机的不同线程,不能直接比较指针 - **正确实现**: ```cpp bool is_running_in_coroutine(lua_State* L) { // 从注册表获取主线程 lua_rawgeti(L, LUA_REGISTRYINDEX, ref_main); lua_State* main = lua_tothread(L, -1); lua_pop(L, 1); // 清理栈 // 当前线程 != 主线程 ⇒ 在协程中 return (L != main); } ``` #### 3. **引用键无效或错误** ```cpp // 错误示例:使用了无效引用 lua_rawgeti(L, LUA_REGISTRYINDEX, 0); // 0是无效引用 ``` - **问题**:`luaL_ref` 返回的引用键需妥善保存 - **调试建议**: ```cpp lua_rawgeti(L, LUA_REGISTRYINDEX, ref_main); if (lua_isnil(L, -1)) { printf("主线程引用失效! ref=%d\n", ref_main); } ``` #### 4. **协程创建方式错误** ```cpp // 错误:未通过 lua_newthread 创建 lua_State* co = L; // 错误复用主线程 ``` - **关键点**:只有通过 `lua_newthread()` 创建的才是协程[^1] - **正确创建**: ```cpp lua_State* co = lua_newthread(L); // 正确创建协程 ``` ### 完整解决方案 ```cpp class LuaLibThread { static int ref_main; // 主线程引用 public: static void init(lua_State* L) { lua_pushthread(L); ref_main = luaL_ref(L, LUA_REGISTRYINDEX); } static bool is_running_in_coroutine(lua_State* L) { // 获取注册表中的主线程 lua_rawgeti(L, LUA_REGISTRYINDEX, ref_main); if (lua_isnil(L, -1)) { // 引用失效检查 lua_pop(L, 1); return false; } lua_State* main = lua_tothread(L, -1); lua_pop(L, 1); // 清理栈 return (L != main); // 当前线程≠主线程 ⇒ 在协程中 } }; // 静态成员初始化 int LuaLibThread::ref_main = LUA_NOREF; ``` ### 关键验证点 1. **初始化检查**: ```cpp // 在 Lua 状态初始化后调用 LuaLibThread::init(main_L); ``` 2. **协程测试**: ```cpp lua_State* co = lua_newthread(main_L); bool in_coroutine = LuaLibThread::is_running_in_coroutine(co); // 此时应返回 true ``` > 协程本质是协作式多任务,必须通过 `lua_newthread` 显式创建[^4]。若未正确保存主线程引用或错误比较状态指针,会导致检测始终失败。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值