关于lua_pcall执行luaL_loadstring加载代码时栈状态的疑问
Lua调用加载代码时保持lua_State状态不变的解决方案
问题场景
测试代码意图通过luaL_loadstring加载两段Lua代码,多次执行这两段代码且保持lua_State的栈状态不变,但实际执行结果与预期不符。
测试代码
int f(lua_State *L) { std::cout << std::format("- Function call, stack size: {}, parameter value: {}\n", lua_gettop(L), lua_tointeger(L, 1)); return 0; } int main() { lua_State *const L = luaL_newstate(); luaL_loadstring(L, "f1(1)"); luaL_loadstring(L, "f2(2)"); lua_register(L, "f1", f); lua_register(L, "f2", f); std::cout << "\nStack before call:\n"; for (int b = 0, e = lua_gettop(L); b != e; ++b) std::cout << '\t' << lua_typename(L, lua_type(L, b + 1)) << '\n'; for (int i = 0; lua_pcall(L, 0, LUA_MULTRET, 0) == 0; ++i) { std::cout << "\nStack on iteration " << i << '\n'; for (int b = 0, e = lua_gettop(L); b != e; ++b) std::cout << '\t' << lua_typename(L, lua_type(L, b + 1)) << '\n'; } std::cout << "Before exit: " << lua_tostring(L, -1); return 0; }
实际输出
Stack before call: function function - Function call, stack size: 1, parameter value: 2 Stack on iteration 0 function - Function call, stack size: 1, parameter value: 1 Stack on iteration 1 Before exit: attempt to call a nil value
错误理解总结
- 误以为多次
luaL_loadstring会将代码拼接合并到lua_State中; - 误以为
lua_pcall执行后lua_State的栈状态不会改变。
核心问题
如何在保持lua_State状态(保留加载的函数)的情况下,执行通过luaL_loadstring加载的代码?
解决方案
首先明确Lua API的实际行为:
luaL_loadstring的作用是将Lua代码编译为独立的函数对象并压入栈顶,多次调用会在栈中生成多个独立函数,而非拼接代码;lua_pcall会弹出栈顶的函数执行,执行完毕后该函数会从栈中移除,因此栈状态必然变化。
针对需求,提供两种可行方案:
方案1:复制栈中函数后执行
通过lua_pushvalue复制栈中的目标函数到栈顶,然后调用lua_pcall执行副本,这样原函数会保留在栈中,不会被消耗。修改循环部分代码如下:
int main() { lua_State *const L = luaL_newstate(); luaL_loadstring(L, "f1(1)"); luaL_loadstring(L, "f2(2)"); lua_register(L, "f1", f); lua_register(L, "f2", f); std::cout << "\nStack before call:\n"; for (int b = 0, e = lua_gettop(L); b != e; ++b) std::cout << '\t' << lua_typename(L, lua_type(L, b + 1)) << '\n'; // 获取栈中已加载的函数数量 const int func_count = lua_gettop(L); // 循环执行所有函数,保留原栈状态 for (int i = 0; i < func_count; ++i) { // 复制第i+1个函数到栈顶(栈底为第一个加载的f1,索引1对应f1,索引2对应f2) lua_pushvalue(L, i + 1); // 执行复制的函数,执行后副本被弹出,原函数仍在栈中 if (lua_pcall(L, 0, LUA_MULTRET, 0) != 0) { std::cout << "\nError: " << lua_tostring(L, -1); lua_pop(L, 1); // 弹出错误信息,清理栈 break; } std::cout << "\nAfter executing function " << (i+1) << ", stack:\n"; for (int b = 0, e = lua_gettop(L); b != e; ++b) std::cout << '\t' << lua_typename(L, lua_type(L, b + 1)) << '\n'; } // 验证栈状态仍保留原函数 std::cout << "\nFinal stack state:\n"; for (int b = 0, e = lua_gettop(L); b != e; ++b) std::cout << '\t' << lua_typename(L, lua_type(L, b + 1)) << '\n'; lua_close(L); return 0; }
方案2:将加载的函数存入全局表/注册表
把编译后的函数存入全局变量或Lua注册表,后续执行时从存储位置取出函数调用,原函数不会被消耗,适合需要重复执行的场景:
int main() { lua_State *const L = luaL_newstate(); // 加载函数并存入全局表 luaL_loadstring(L, "f1(1)"); lua_setglobal(L, "loaded_func1"); // 将栈顶函数存入全局变量loaded_func1 luaL_loadstring(L, "f2(2)"); lua_setglobal(L, "loaded_func2"); // 存入全局变量loaded_func2 lua_register(L, "f1", f); lua_register(L, "f2", f); // 第一次执行 std::cout << "\nFirst execution:\n"; lua_getglobal(L, "loaded_func1"); lua_pcall(L, 0, LUA_MULTRET, 0); lua_getglobal(L, "loaded_func2"); lua_pcall(L, 0, LUA_MULTRET, 0); // 第二次执行(全局变量仍存在,可重复调用) std::cout << "\nSecond execution:\n"; lua_getglobal(L, "loaded_func1"); lua_pcall(L, 0, LUA_MULTRET, 0); lua_close(L); return 0; }
关键说明
lua_pushvalue(L, idx):复制指定索引的栈元素到栈顶,不会修改原元素的位置和状态;- 全局表/注册表存储:适合长期保留需要重复执行的函数,避免频繁操作栈索引;
- 错误处理:执行
lua_pcall失败后,需用lua_pop弹出栈顶的错误信息,防止栈状态混乱。
内容的提问来源于stack exchange,提问作者PaperBirdMaster
相关产品推荐
相关产品推荐

