使用C API实现弱引用表和垃圾回收终结器

我正在尝试使用C API将函数值存储在弱表中,以创建一个GC finalizer。

我首先在纯Lua 5.2中编写了一个原型:

local function myfinalizer()
   print 'Called finalizer'
end

function myfunc()
   print 'Called myfunc'
end

local sentinels = setmetatable({}, { __mode='k' })
sentinels[myfunc] = setmetatable({}, { __gc=myfinalizer })

myfunc()
myfunc = nil
collectgarbage 'collect'

print 'Closing Lua'

结果输出为:

Called myfunc
Called finalizer
Closing Lua

原型似乎按预期工作。以下是C版本:

#include <stdlib.h>
#include <stdio.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

static int my_finalizer(lua_State *L)
{
    puts("Called finalizer");
    return 0;
}

static int my_func(lua_State *L)
{
    puts("Called myfunc");
    return 0;
}

int main(void)
{
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);

    // create sentinels table (weak keys) in registry
    lua_newtable(L);                                    // t
    lua_newtable(L);                                    // t mt
    lua_pushstring(L, "k");                             // t mt v
    lua_setfield(L, -2, "__mode");                      // t mt
    lua_setmetatable(L, -2);                            // t
    lua_setfield(L, LUA_REGISTRYINDEX, "sentinels");    //

    // push global function and register as sentinel
    lua_pushcfunction(L, my_func);                      // f
    lua_getfield(L, LUA_REGISTRYINDEX, "sentinels");    // f t
    lua_pushvalue(L, 1);                                // f t k
    lua_newuserdata(L, 0);                              // f t k v
    lua_newtable(L);                                    // f t k v mt
    lua_pushcfunction(L, my_finalizer);                 // f t k v mt v
    lua_setfield(L, -2, "__gc");                        // f t k v mt
    lua_setmetatable(L, -2);                            // f t k v
    lua_settable(L, -3);                                // f t
    lua_pop(L, 1);                                      // f
    lua_setglobal(L, "myfunc");                         //

    // execute test script and exit
    if (luaL_dostring(L, "myfunc(); myfunc=nil; collectgarbage'collect'")) {
        printf("Error: %s\n", lua_tostring(L, -1));
    }
    lua_gc(L, LUA_GCCOLLECT, 0);    // suggestion: two full gc cycles
    fflush(stdout);                 // suggestion: immediate flush
    puts("Closing Lua");
    lua_close(L);

    fflush(stdout);
    return EXIT_SUCCESS;
}

使用以下命令进行编译:

$ gcc -std=c99 -Wall -Werror -pedantic -O2 -o main main.c -ldl -llua52 -lm

输出结果为:

Called myfunc
Closing Lua
Called finalizer

C版本有一些小的不同:

  1. 代替本地sentinels表,我将其存储在注册表中。
  2. 使用零大小的用户数据而不是具有__gc元方法的表作为标记值。

我困惑的是,为什么在运行完整的收集周期后,C版的myfunc finalizer没有执行。我做错了什么?

点赞
用户3677376
用户3677376

根据Lua手册所述:

只有显式构造的对象才会从弱表中删除。 值,例如数字和轻量级C函数,不会受到垃圾收集的影响,因此不会从弱表中删除(除非其关联值被收集)。

您的my_func被推入而没有任何upvalue,因此它是一个轻量级C函数,并且在垃圾收集期间不会从弱表中删除,因此关联的userdata在关闭Lua状态之前不会成为垃圾。 如果您使用Lua函数而不是my_func,或者如果您使用upvalues推入my_func(并且如果您修复lua_gc调用中参数的顺序!),则您的代码应该正常工作。

总之,以下值类型_不会_从弱表中删除(假设它们的关联键/值也没有被删除):

  • 布尔值
  • 数字
  • 字符串
  • 轻量级userdata
  • 轻量级C函数(仅适用于Lua 5.2)

因此,由于没有轻量级C函数,因此您的程序应该与Lua 5.1正常工作(仍需修复lua_gc调用)。

2014-06-06 23:40:46