2016-10-02 5 views
3

В Lua создается C closure с указателем функции и дополнительными значениями (upvalues ​​), которые будут доступны при вызове закрытия.Как я могу оставить закрытие функции Lua в хвостовом вызове?

В моем приложении я использую эту функцию, чтобы передать таблицу методов геттера методу __index. Если ключ существует как метод, он будет вызываться, передавая его исходные параметры. Если я вызываю функцию напрямую, то как-то выше значения все еще доступны для вызываемого абонента и, таким образом, выполняются при одном и том же закрытии функции.

Можно ли оставить функцию закрытия или каким-либо образом стереть верхние значения? Цель состоит в том, чтобы уменьшить ненужное воздействие повышений без существенных накладных расходов.

Это 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

Вызов функции через 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

Разве это не просто создаст новое закрытие? Как насчет возвращаемых значений (это компилирует btw, 'lua_call' является недействительным). В моем случае я, вероятно, могу быть уверен, что всегда есть нулевые или одно возвращаемые значения, но может ли это быть обобщено? (См. Также обновленные требования по сохранению аргументов через стек) – Lekensteyn

+0

@Lekensteyn Обновлено для возвращаемых значений; Я бы предположил, что в этом примере вы не хотите использовать те же аргументы, поскольку это просто передаст функцию себе, что бессмысленно. – immibis

+0

Спасибо, но я действительно хочу передать исходные параметры. В реальном случае у меня есть 'metatable .__ newindex (obj, key, value)', он должен вызывать 'setter (obj, value)', где 'setter' находится через upvalue. Предложение 'lua_gettop (L)' before/after полезно найти возвращаемые значения, но если я изменю его, чтобы сохранить params ('lua_call (0, lua_gettop (L), LUA_MULTRET)'), то он выдает ошибку: lapi.c: 895: lua_callk: Assertion '((nargs + 1) < (L-> top - L-> ci-> func)) && "недостаточно элементов в стеке"' failed '. Есть идеи? – Lekensteyn

0

Lua upvalues ​​действительно ограничивается закрытием, вы не может получить доступ превышения в других затворах. Закрытие вводится путем вызова C-закрытия (создается через lua_pushcclosure) или путем вызова закрытия Lua (созданного с помощью "block", lua_load и т. Д.).

Чтобы ввести новое замыкание в C, используйте семейство функций lua_call, как описано в immibis' answer.

Еще одна идея - «стереть» верхние значения из текущей области. Это не будет иметь желаемого результата, так как upvalues ​​привязаны к замыканию, поэтому, как только вы его очистите (установив значение nil), он будет сохраняться до следующего вызова (это то, что Programming in Lua documentation means by equivalence to a static variable).

Возникает новый вопрос: можно ли как-то перезаписать существующее закрытие, вызвав другую функцию из функции 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 

Чтобы уменьшить накладные расходы, просто повторно использовать текущее закрытие ("область видимости функции «). Если скрывать локальные значения, важно (чтобы предотвратить случайную модификацию), затем вызовите закрытие C. В любом случае вы не можете напрямую обращаться к другим значениям с помощью pseudoindexes, полученных из lua_upvalueindex, для этого вместо этого потребуется запрос через lua_getupvalue.

Смежные вопросы