文章目录

  因为lua和c的内存回收管理方式不同,所以使用c+lua的结构做开发时我们可以自由选择c创建出的供lua使用的对象(我们这里说的对象都是c中分配的堆变量)到底该由lua还是c来释放。我参与的前一个项目因为历史遗留问题选择了让c来释放对象的方案,也就是写两个lua_CFunction来封装c中一个对象的创建和销毁给lua用,做法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
typedef struct Packet{
};
//c code
int creatPacket(lua_State* L) {
Packet* pack = (Packet*)malloc(sizeof(Packet));
Packet** ud = (Packet**)lua_newuserdata(L, sizeof(Packet*));
*ud = pack;
return 1;
}
int freePacket(lua_State* L) {
Packet** ud = (Packet**)lua_touserdata(L, -1);
if( ud ) {
free(*ud);
}
return 0;
}
static const luaL_Reg packetlib[] = {
{"creatPacket", creatPacket},
{"freePacket", freePacket},
{NULL, NULL}
};
int luaopen_packet(lua_State *L) {
luaL_register(L, packetlib);
return 1;
}
1
2
3
-- lua code
local pack = creatPacket()
freePacket(pack) --要主动调用函数销毁对象

  这样做很符合c的使用习惯,但是却不符合lua的使用习惯,当我们在lua中使用的时候很可能忘记主动调用销毁函数去销毁对象,从而造成内存泄漏。这并不是危言怂听,我们项目曾经就因为这个原因出现过内存泄漏。所以我后面下决心修改掉了这些东西,选择让lua的gc帮我们自动释放c创建出的对象,这样我们在使用lua的时候就能够完全按照lua的习惯去开发了,大家也不必再去担心使用lua时忘记释放c对象而导致内存泄漏。
  让lua的gc替我们回收c创建出的对象其实很简单,基本原理就是为c对象所绑定的userdata分配一个存在自定义__gc方法的metatable,这个__gc方法的作用就是为我们回收c对象。下面是个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//c code
static int freePacket(lua_State* L) {
Packet** ud = (Packet**)lua_touserdata(L, -1);
if( ud ) {
free(*ud);
}
return 0;
}
static void regPacketMetatable(lua_State *L) {
luaL_newmetatable(L, "packetMetatable"); //为packet创建metatable
lua_pushstring(L, "__gc");
lua_pushcfunction(L, freePacket);
lua_rawset(L, -3); //注册__gc方法
lua_pop(L, 1); //弹出元表
}
int creatPacket(lua_State* L) {
Packet* pack = (Packet*)malloc(sizeof(Packet));
Packet** ud = (Packet**)lua_newuserdata(L, sizeof(Packet*));
*ud = pack;
luaL_getmetatable(L, "packetMetatable"); //将元表放到栈顶
lua_setmetatable(L, -2); //为userdata设置元表
return 1;
}
static const luaL_Reg packetlib[] = {
{"creatPacket", creatPacket},
{NULL, NULL}
};
int luaopen_packet(lua_State *L) {
luaL_register(L, packetlib);
regPacketMetatable(L); //不要忘记先把元表创建出来
return 1;
}
1
2
-- lua code
local pack = creatPacket() --不用再管他了,lua的gc会替我们搞定

  整个代码只是增加了一些对元表和__gc元方法的处理,但却能让我们在lua中更简单的进行开发,不用再顾忌创建c对象会带来内存泄露的问题。为什么这样处理后packet对象会被lua的gc自动回收呢?原理主要是通过gc回收对象时触发__gc元方法实现的。首先,我们为绑定c对象所创建的userdata会被lua的gc所回收,而在gc进行回收时会触发元表中我们自定义的__gc元方法,我们自定义的这个__gc方法的作用就是释放掉userdata所绑定的c对象,因此我们就达到了让lua的gc替我们销毁对象的作用了。
  这两种方法并不存在谁优谁略,只是看你想要什么样的效果。比如我要的就是开发更便利,消除忘记主动销毁对象带来的内存泄漏,所以我选择让lua的gc替我回收对象。而如果你的要求不同,比如你有个对象池,希望能够在不用对象时及时的放入对象池,那你最好还是自己手动调用回收函数。上面的代码是随手写的,没有测试过,不过思路就是这样的,大家只要明白思路就可以了,至于选择哪种方案,那就要你自己决定了。

文章目录