如何在尾调用中留下Lua函数闭包?

时间:2016-10-02 20:37:26

标签: c lua

在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);
}

2 个答案:

答案 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进行查询。