2016-10-02 3 views
3

ルアでは、関数ポインタと、クロージャが呼び出されたときに使用可能な追加の値(upvalues)を使用してC closureが作成されます。テールコールでLua関数クロージャを残すにはどうしたらいいですか?

私のアプリケーションでは、ゲッターメソッドテーブルを__indexメタメソッドに渡すためにこの機能を使用します。メソッドとしてキーが存在する場合、キーは元のパラメータを渡して呼び出されます。関数を直接呼び出すと、何らかの形でアップルの値が呼び出され、同じ関数クロージャで実行されます。

関数クロージャを終了するか、何らかの理由でアップルを消去することはできますか?目標は、重大なオーバーヘッドを招くことなく、上昇値の不必要な露出を減らすことです。

MWD(42行)は、この問題を強調表示するTODOの問題を示しています。現在、upvalue: 42が2回印刷されています。期待される結果は、1つのupvalue: 42ともう1つの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

代わりに、直接C関数を呼び出すのでは、Luaの通過関数を呼び出します(注:未テストコード)

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

これは単純に新しいクロージャを作成しませんか?戻り値はどうですか(これはコンパイル時に 'lua_call'が無効です)。私の場合、おそらく常に0または1つの戻り値があることを確かめることができますが、これを一般化することはできますか? (スタックによる引数の保存に関する更新された要件も参照してください) – Lekensteyn

+0

@Lekensteyn戻り値のために更新されました。この例では、同じ引数を使用したくないと思います。それは、関数自体を意味のないものに渡すだけなので、私は考えています。 – immibis

+0

ありがとうございますが、私は実際に元のパラメータを渡したいと思います。実際には、私は 'metatable .__ newindex(obj、key、value)'を持っているので、setter(obj、value)を呼び出すべきです。 'lua_gettop(L)'前/後の提案は返された値を見つけるのは良いですが、 'lua_call(0、lua_gettop(L)、LUA_MULTRET)')を保持するように変更した場合、 lapi.c:895:lua_callk:Assertion '((nargs + 1)< (L-> top-L-> ci-> func))&& "スタックの要素が不足しています。何か案が? – Lekensteyn

0

Luaの上位値が実際に閉鎖に制限されている、あなたはアクセスできません他のクロージャーの値上がり。クロージャは、クロージャ(lua_pushcclosureで作成)を呼び出すか、Luaクロージャ("block"lua_loadなどで作成)を呼び出すことによって入力されます。

Cで新しいクロージャを入力するには、immibis' answerで説明されているように、lua_callファミリの関数を使用します。

もう一つのアイデアは、現在のスコープからupvaluesを "消去"することです。 upvaluesはクロージャーにバインドされているので、これを望みの結果にすることはできません。値をnilに設定してクリアすると、次の呼び出しまで続きます(これはProgramming in Lua documentation means by equivalence to a static variableです)。

C関数から別の関数を呼び出すことで、既存のクロージャを上書きすることができるのでしょうか?答えはいいえだ。クロージャが呼び出されると、最終的にluaD_precallが呼び出されます。

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 */ 

のみ関数から戻った後、それは前回の「関数スコープ」に移動します:

このC関数は基本的に現在のLuaの状態で、新たな「関数スコープ」を作成し、その後、Cの関数ポインタを呼び出します
int luaD_poscall (lua_State *L, StkId firstResult) { 
    // ... 
    CallInfo *ci = L->ci; 
    // ... 
    L->ci = ci = ci->previous; /* back to caller ("function scope") */ 

プログラムカウンタを何らかのカスタム関数に変更することができない限り、現在のクロージャの外で関数を呼び出すことはできません。さらに、新しい問題が発生します。関数が親クロージャで実行されるようになりました。この時点での2つのオプションは、2番目の関数が現在のスコープで単に実行されること、または新しいスコープを作成することを受け入れることです。

のLuaで同様の質問はスコープを再利用するかどうかです:

function myfunc() 
    print("myfunc") 
    print("otherfunc") 
end 

または新しいものを作成するために:

function myfunc() 
    print("myfunc") 
    do -- note: new Lua closure created 
     print("otherfunc") 
    end 
end 

オーバーヘッドを減らすために、単に現在閉鎖(「関数スコープを再利用")。ローカルの上位値を隠すことが重要である場合(誤った変更を防ぐため)、Cクロージャを呼び出します。いずれにしても、lua_upvalueindexから得られた疑似索引を介して他の上位値に直接アクセスすることはできませんが、代わりにlua_getupvalueを介してクエリが必要になります。

関連する問題