Page's Personal Website

Tolua++中的垃圾回收

2021-05-06
lua

[TOC]

想知道tolua++导出的函数是怎么实现垃圾回收的,如何和元方法__gc关联上的。

疑虑

  • lua的gc

看代码,用到的关键字是”.collector”,而lua的gc是通过修改元方法__gc实现的

Garbage userdata with a field __gc in their metatables are not collected immediately by the garbage collector. Instead, Lua puts them in a list. After the collection, Lua does the equivalent of the following function for each userdata in that list:

     function gc_event (udata)
       local h = metatable(udata).__gc
       if h then
         h(udata)
       end
     end
来源: http://www.lua.org/manual/5.1/manual.html#2.10.1
  • tolua++里面的gc相关代码

用的是tolua_register_gc,把对象指针放在”tolua_gc”表中

/* Do clone
*/
TOLUA_API int tolua_register_gc(lua_State* L, int lo)
{
    int success = 1;
    void* value = *(void**)lua_touserdata(L, lo);
    lua_pushstring(L, "tolua_gc");
    lua_rawget(L, LUA_REGISTRYINDEX);
    lua_pushlightuserdata(L, value);
    lua_rawget(L, -2);
    if (!lua_isnil(L, -1)) /* make sure that object is not already owned */
        success = 0;
    else
    {
        lua_pushlightuserdata(L, value);
        lua_getmetatable(L, lo);
        lua_rawset(L, -4);
    }
    lua_pop(L, 2);
    return success;
}

那么是如何gc的呢?

解惑

tolua_gc

首先tolua++导出的时候会导出各种表,其中就有我们关系的tolua_gc。

  • tolua_open会创建一个闭包,这个闭包就用到了tolua_gc这个表
        /* create gc_event closure */
        lua_pushstring(L, "tolua_gc_event");
        lua_pushstring(L, "tolua_gc");
        lua_rawget(L, LUA_REGISTRYINDEX);
        lua_pushstring(L, "tolua_super");
        lua_rawget(L, LUA_REGISTRYINDEX);
        lua_pushcclosure(L, class_gc_event, 2);
        lua_rawset(L, LUA_REGISTRYINDEX);

class_gc_event

这个代码大概看一下就明白了,就是调用.collector函数

TOLUA_API int class_gc_event (lua_State* L)
{
    void* u = *((void**)lua_touserdata(L,1));
    int top;
    lua_pushvalue(L, lua_upvalueindex(1));
    lua_pushlightuserdata(L,u);
    lua_rawget(L,-2);            /* stack: gc umt    */
    lua_getmetatable(L,1);       /* stack: gc umt mt */
    /*fprintf(stderr, "checking type\n");*/
    top = lua_gettop(L);
    if (tolua_fast_isa(L,top,top-1, lua_upvalueindex(2))) /* make sure we collect correct type */
    {
        /*fprintf(stderr, "Found type!\n");*/
        /* get gc function */
        lua_pushliteral(L,".collector");
        lua_rawget(L,-2);           /* stack: gc umt mt collector */
        if (lua_isfunction(L,-1)) {
            /*fprintf(stderr, "Found .collector!\n");*/
        }
        else {
            lua_pop(L,1);
            /*fprintf(stderr, "Using default cleanup\n");*/
            lua_pushcfunction(L,tolua_default_collect);
        }
        lua_pushvalue(L,1);         /* stack: gc umt mt collector u */
        lua_call(L,1,0);
        lua_pushlightuserdata(L,u); /* stack: gc umt mt u */
        lua_pushnil(L);             /* stack: gc umt mt u nil */
        lua_rawset(L,-5);           /* stack: gc umt mt */
    }
    lua_pop(L,3);
    return 0;
}

这个函数做了两件事情 (1)先从tolua_gc表中取出来,然后调用.collector进行内存释放 也就是前面几句代码,其中lua_upvalueindex(1)就对应了tolua_gc表了

    lua_pushvalue(L, lua_upvalueindex(1));
    lua_pushlightuserdata(L,u);
    lua_rawget(L,-2);            /* stack: gc umt    */
    lua_getmetatable(L,1);       /* stack: gc umt mt */

(2)如果有继承关系,需要保证取的是正确的指针,这里就用到了tolua_super表了

那么这个.collector是如何来的?

.collector注册

  • tolua_cclass

使用tolua_cclass注册gc函数

// 注册
  tolua_cclass(tolua_S,"KVec3","KVec3","",tolua_collect_KVec3);
// gc函数
static int tolua_collect_KVec3 (lua_State* tolua_S)
{
 KVec3* self = (KVec3*) tolua_tousertype(tolua_S,1,0);
    Mtolua_delete(self);
    return 0;
}
  • push_collector

tolua_cclass中会调用push_collector函数,就是把.collector函数注册到类名(KVec3等)的元表中

static void push_collector(lua_State* L, const char* type, lua_CFunction col) {
    /* push collector function, but only if it's not NULL, or if there's no
       collector already */
    if (!col) return;
    luaL_getmetatable(L, type);
    lua_pushstring(L, ".collector");

    lua_pushcfunction(L, col);
    lua_rawset(L, -3);
    lua_pop(L, 1);
};

现在就只要看__gc和这个.collector是如何关联的

tolua_classevents

tolua_open中会调用tolua_newmetatable方法,其中会调用tolua_classevents函数

tolua_newmetatable(L, "tolua_commonclass");
  • tolua_classevents

这个函数就是绑定__gc元方法的

    lua_pushstring(L,"__gc");
    lua_pushstring(L, "tolua_gc_event");
    lua_rawget(L, LUA_REGISTRYINDEX);
    /*lua_pushcfunction(L,class_gc_event);*/
    lua_rawset(L,-3);

代码很清楚,元方法就是tolua_gc_event,这和最前面的tolua_open中注册的闭包就接上了

总结

总结一下这几个地方的思路

  • 1.一开始注册一个闭包,其中有一个tolua_gc的表,这个表是用来存c++对象指针的

  • 2.每次创建local对象的时候,会把c++指针放在这个tolua_gc的表中,通过调用tolua_register_gc方法注册。

  • 3.每次lua的对象gc之后,会调用对象注册的__gc元方法,这个元方法class_gc_event是一个闭包

  • 4.这个闭包里面存了两个upvalue值,tolua_gc和tolua_super。tolua_super在tolua_fast_isa会用到,因为实现了继承,所以主要是找到正确的对象

  • 5.什么时候用到tolua_gc呢,如果c++对象没有调用tolua_register_gc注册到tolua_gc表中。即使每次lua对象gc触发调用了class_gc_event闭包中的.collector函数,也没有释放,因为从tolua_gc中没有取到要删除的对象


Comments

Content