在Lua中,使用函数指针和其他值( upvalues )创建C closure,这些值在调用闭包时可用。
在我的应用程序中,我使用此功能将getter方法表传递给__index
元方法。如果密钥作为方法存在,则将调用它,并传递其原始参数。如果我直接调用函数,那么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);
}
答案 0 :(得分:2)
通过Lua调用该函数,而不是直接调用C函数:
static int myfunc(lua_State *L) {
// cfunc still used for printing here
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)));
// call the C function as if it was a Lua function
int stackSizeBefore = lua_gettop(L);
lua_call(L, 0, LUA_MULTRET);
return lua_gettop(L) - stackSizeBefore;
}
(注意:未经测试的代码)
答案 1 :(得分:0)
Lua upvalues实际上仅限于一个闭包,你不能在其他闭包中访问upvalues。通过调用C闭包(通过lua_pushcclosure
创建)或通过调用Lua闭包(通过"block",lua_load
等创建)来输入闭包。
要在C中输入新的闭包,请使用lua_call
中所述的immibis' answer系列函数。
另一个想法是“擦除”当前范围的上升值。由于upvalues被绑定到一个闭包,因此这将没有所需的结果,所以一旦你“清除”它(通过将值设置为nil
),它将持续到下一次调用(这就是{{3 }})。
出现了一个新问题,是否可以通过调用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 -- note: new Lua closure created
print("otherfunc")
end
end
为了减少开销,只需重用当前的闭包(“函数范围”)。如果隐藏本地upvalues很重要(防止意外修改),则调用C闭包。在任何情况下,您都无法通过Programming in Lua documentation means by equivalence to a static variable获取的伪索引直接访问其他upvalues,而是需要通过lua_upvalueindex
进行查询。