【发布时间】:2014-07-28 01:57:27
【问题描述】:
我正在尝试通过使用 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 版本有一些细微差别:
- 我在注册表中存储的不是本地
sentinels表。 - 通过
__gc元方法使用零大小的用户数据而不是表作为标记值。
我很困惑为什么在 C 版本中 myfunc 终结器在运行完整的收集周期后不执行。我做错了什么?
【问题讨论】:
-
尝试在原型代码中使用 userdata 代理,看看是否得到相同的行为?也尝试在 C 代码中使用表代理?从 luaL_dostring 返回后尝试调用 collect ?如果您使用两个 collectgarbage 调用,结果会改变吗?
-
@EtanReisner 感谢您的建议。从
luaL_dostring返回后,我尝试添加lua_gc(L, 0, LUA_GCCOLLECT),并用表格替换用户数据,我得到了相同的结果。 -
将 fflush 移到 lua_close 之前是否会更改输出顺序?这可能是输出刷新问题吗?虽然我还记得在某些情况下需要两个完整的周期,但我不明白为什么使用 C 会有所不同。
-
@EtanReisner 似乎没有任何效果。我根据您的建议更新了发布的 C 代码。
-
@Adam 一个小小的观察——它也可以用 Lua5.1 重现。您应该使用
$ gcc -std=c99 -Wall -Werror -pedantic -O2 -o main main.c -llua52 -ldl -lm,因为对于较新版本的 GCC - 链接参数的顺序很重要。
标签: c lua garbage-collection lua-api