弱表和GC终结器使用C API

6
我将尝试使用C API将函数值存储在弱表中,以创建GC终结器。我开始先在纯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 终结器没有执行。我做错了什么?


在原型代码中尝试使用userdata代理,看看是否会得到相同的行为?在C代码中也尝试使用table代理?在从luaL_dostring返回后调用collect,然后再尝试一下?如果使用两个collectgarbage调用,结果会改变吗? - Etan Reisner
@EtanReisner 感谢您的建议。我尝试在从 luaL_dostring 返回后添加了 lua_gc(L, 0, LUA_GCCOLLECT),以及将 userdata 替换为 table,但结果仍然相同。 - Adam
将您的fflush移动到lua_close之前是否会改变输出顺序?这可能是一个输出刷新问题吗?虽然我还记得在某些情况下需要两个完整周期,但我不明白为什么使用C会有所不同。 - Etan Reisner
@EtanReisner 看起来没有任何效果。我根据您的建议更新了发布的C代码。 - Adam
@Adam 一个小观察 - 这也可以在Lua5.1中重现。而且您应该使用 $ gcc -std=c99 -Wall -Werror -pedantic -O2 -o main main.c -llua52 -ldl -lm,因为在较新版本的GCC中,链接参数的顺序很重要。 - Kamiccolo
@Kamiccolo 很好知道,感谢您验证了Lua 5.1中的行为。 - Adam
1个回答

5
根据Lua手册状态,只有明确构造的对象才会从弱表中移除。例如数字和轻量级C函数等值不受垃圾回收的影响,因此它们不会从弱表中移除(除非其关联值被回收)。由于您的my_func没有任何upvalue,因此它是一个轻量级C函数,在垃圾回收期间不会从弱表中移除,因此在关闭Lua状态之前,关联的用户数据不会成为垃圾。如果您使用Lua函数代替my_func,或者如果您使用upvalue推送my_func(并且如果您修复lua_gc调用中参数的顺序),则您的代码应该可以工作。总之,以下值类型(假设其关联键/值未被删除)不会从弱表中移除:
  • 布尔值
  • 数字
  • 字符串
  • 轻量级用户数据
  • 轻量级C函数(仅适用于Lua 5.2)
因此,您的程序应该能够与Lua 5.1正常工作,因为没有轻量级C函数(您仍然需要修复lua_gc调用)。

将该函数变为闭包后,现在按预期工作了,感谢您的出色答案。 - Adam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接