如何在 Lua 函数闭包中使用尾调用?
在 Lua 中,可以通过函数指针和额外的值(_upvalues_)创建 C 闭包,当闭包被调用时,这些值将可用。
在我的应用程序中,我使用这个功能将 getter 方法表传递给 __index 元方法。如果键存在为方法,则会调用该方法并传递其原始参数。如果我直接调用该函数,则闭包内的 upvalues 仍然可用于被调用者,因此在同一函数闭包中执行。
是否可以离开函数闭包或以某种方式消除 upvalues?目标是减少不必要的 upvalues 暴露,而不会引入重大开销。
这是一个 MWE(42 行),演示了问题,并用 TODO 强调了问题。目前会打印两个 upvalue: 42。预期结果是一个 upvalue: 42 和另一个 upvalue: 0(表示无效值)。
#include <stdlib.h>
#include <stdio.h>
#include <lua.h>
static void *allocfn(void *ud, void *ptr, size_t osize, size_t nsize) {
if (nsize == 0) {
free(ptr);
return NULL;
} else {
return realloc(ptr, nsize);
}
}
static int myfunc(lua_State *L) {
lua_CFunction cfunc = lua_tocfunction(L, 1);
printf("myfunc called with %p\n", cfunc);
printf("upvalue: %d\n", (int)lua_tointeger(L, lua_upvalueindex(1)));
// TODO how to drop upvalue (tail call, leaving the closure)?
return cfunc(L);
}
static int otherfunc(lua_State *L) {
printf("otherfunc called with %p\n", lua_tocfunction(L, 1));
printf("upvalue: %d\n", (int)lua_tointeger(L, lua_upvalueindex(1)));
return 0;
}
int main() {
lua_State *L = lua_newstate(allocfn, NULL);
/* Create closure for myfunc with upvalue 42. */
lua_pushinteger(L, 42);
lua_pushcclosure(L, myfunc, 1);
/* Argument 1 for myfunc. */
lua_pushcclosure(L, otherfunc, 0);
/* Invoke myfunc(otherfunc) with "42" in its closure. */
lua_call(L, 1, 0);
lua_close(L);
}
Lua 的 upvalues 实质上仅限于闭包内使用,你无法在其他闭包中访问 upvalues。调用 C 闭包(通过 lua_pushcclosure 创建)或调用 Lua 闭包(通过 "block"、lua_load 等创建)可以进入闭包。
要在 C 中进入新的闭包,可以使用 lua_call 函数系列来调用,详见 immibis' 答案。
另一个想法是“擦除”当前范围内的 upvalues。这样做无法达到预期效果,因为 upvalues 绑定在闭包中,因此一旦你“清除”它(将值设置为 nil),它将持久到下一次调用(这就是 Programming in Lua 文档中等同于静态变量的含义)。
于是又出现了一个新问题:能否通过从 C 函数调用另一个函数来覆盖现有的闭包?答案是不行。当闭包被调用时,它最终会调用 luaD_precall。此 C 函数基本上在当前 Lua 状态上创建一个新的“函数范围”,然后调用 C 函数指针:
int luaD_precall (lua_State *L, StkId func, int nresults) {
lua_CFunction f;
CallInfo *ci;
// ...
switch (ttype(func)) {
// ...
case LUA_TCCL: { /* C closure */
f = clCvalue(func)->f;/* obtain C function pointer */
// ...
ci = next_ci(L); /* now 'enter' new function ("nested scope") */
// ...
n = (*f)(L); /* do the actual call to a C function */
// ...
luaD_poscall(L, L->top - n);
return 1; /* not Lua code, do not invoke Lua bytecode */
只有在从函数返回后,它才会移动回到之前的“函数范围”:
int luaD_poscall (lua_State *L, StkId firstResult) {
// ...
CallInfo *ci = L->ci;
// ...
L->ci = ci = ci->previous; /* back to caller ("function scope") */
除非在某种方式下改变程序计数器到某个自定义函数,否则你将无法在当前闭包之外调用你的函数。此外,你将会有一个新问题:函数现在在父闭包中执行。此时的两种选择是接受第二个函数仅在当前范围内执行,或者创建一个新范围。
在 Lua 中类似的问题是是否重用范围:
function myfunc()
print("myfunc")
print("otherfunc")
end
或者创建一个新的范围:
function myfunc()
print("myfunc")
do -- 注意:创建了新的 Lua 闭包
print("otherfunc")
end
end
为了减少开销,可以简单地重用当前闭包(“函数范围”)。如果隐藏本地 upvalues 很重要(以防止意外修改),则调用 C 闭包。无论如何,你都不能通过 lua_upvalueindex 获取其他 upvalues 的伪索引直接访问它们,这需要通过 lua_getupvalue 进行查询。
- Lua 虚拟机加密load(string.dump(function)) 后执行失败问题如何解决
- 我想创建一个 Nginx 规则,禁止访问
- 如何将两个不同的lua文件合成一个 东西有点长 大佬请耐心看完 我是小白研究几天了都没搞定
- 如何在roblox studio中1:1导入真实世界的地形?
- 求解,lua_resume的第二次调用继续执行协程问题。
- 【上海普陀区】内向猫网络招募【Skynet游戏框架Lua后端程序员】
- SF爱好求教:如何用lua实现游戏内调用数据库函数实现账号密码注册?
- Lua实现网站后台开发
- LUA错误显式返回,社区常见的规约是怎么样的
- lua5.3下载库失败
- 请问如何实现文本框内容和某个网页搜索框内容连接,并把网页输出来的结果反馈到另外一个文本框上
- lua lanes多线程使用
- 一个kv数据库
- openresty 有没有比较轻量的 docker 镜像
- 想问一下,有大佬用过luacurl吗
- 在Lua执行过程中使用Load函数出现问题
- 为什么 neovim 里没有显示一些特殊字符?
- Lua比较两个表的值(不考虑键的顺序)
- 有个lua简单的项目,外包,有意者加微信 liuheng600456详谈,最好在成都
- 如何在 Visual Studio 2022 中运行 Lua 代码?

将函数通过 Lua 调用,而不是直接调用 C 函数:
static int myfunc(lua_State *L) { // 在此仍使用 cfunc 进行打印 lua_CFunction cfunc = lua_tocfunction(L, 1); printf("myfunc called with %p\n", cfunc); printf("upvalue: %d\n", (int)lua_tointeger(L, lua_upvalueindex(1))); // 将 C 函数像调用 Lua 函数一样调用 int stackSizeBefore = lua_gettop(L); lua_call(L, 0, LUA_MULTRET); return lua_gettop(L) - stackSizeBefore; }(注: 未经测试的代码)