Lua中C++自定义类型实例关联的垃圾回收异常问题咨询
问题根因
- 你在C实例中存储的int类型Lua表引用,不属于Lua垃圾回收器的可达性扫描范围:Lua GC只会追踪虚拟机栈、注册表、全局环境、存活full userdata的uservalue这几个路径下的对象,存在原生内存中的普通int值不会被GC识别为有效引用,因此你认为“该Lua表永久存在”的认知是错误的,GC会判定该表无有效引用、可回收,连带存储在表里的第二个C实例也会被标记为回收对象。
- 你创建的存储第一个类型实例的表未生效,通常有两个常见原因:要么该存储表本身没有锚定在Lua的可达路径中(比如只存在C++内存中,没有存入注册表),要么你仅存储了实例的原生指针/轻量用户数据,没有建立实例和其持有的Lua表的绑定关系,GC还是无法追踪到Lua表的存活引用。
修复方案
方案1(Lua 5.2+ 推荐)
使用full userdata的uservalue自动绑定关联:
你给第一个C++实例创建Lua侧的full userdata包装时,直接将其持有的Lua表设为该userdata的uservalue,对应C API代码:
// 假设table在栈上索引为table_idx,userdata在栈上索引为ud_idx lua_pushvalue(L, table_idx); lua_setuservalue(L, ud_idx);
Lua GC会自动追踪full userdata的uservalue,只要userdata本身是存活的,关联的Lua表就不会被回收,表里存储的第二个C++实例自然也会保持存活。
方案2(全版本兼容)
将Lua表锚定到Lua注册表:
用第一个C++实例的内存地址作为唯一key,把关联的Lua表存入注册表,对应C API代码:
// 假设table在栈上索引为table_idx,instance_ptr是第一个C++实例的指针 lua_pushlightuserdata(L, (void*)instance_ptr); lua_pushvalue(L, table_idx); lua_settable(L, LUA_REGISTRYINDEX);
注册表是GC的根扫描对象,存入注册表的表会被永久标记为存活。如果后续你要销毁第一个C++实例,记得主动把注册表中对应的键值对删掉,避免内存泄漏:
lua_pushlightuserdata(L, (void*)instance_ptr); lua_pushnil(L); lua_settable(L, LUA_REGISTRYINDEX);
全局锚定第一个类型实例的正确操作
如果需要全局存储所有第一个类型的实例避免被回收,先在注册表创建一个全局锚定表:
lua_newtable(L); lua_setfield(L, LUA_REGISTRYINDEX, "MyFirstTypeAnchorTable");
每次创建第一个类型的实例时,把实例对应的full userdata存入这个锚定表即可:
lua_getfield(L, LUA_REGISTRYINDEX, "MyFirstTypeAnchorTable"); lua_pushlightuserdata(L, (void*)instance_ptr); // 用实例指针当key lua_pushvalue(L, ud_idx); // 压入实例对应的full userdata lua_settable(L, -3); lua_pop(L, 1); // 弹出锚定表
验证方法
修改完成后可以主动调用lua_gc(L, LUA_GCCOLLECT, 0)触发全量GC,检查第二个C++实例的__gc元方法是否被触发,确认存活状态符合预期。
内容的提问来源于stack exchange,提问作者user16856435
相关产品推荐
相关产品推荐

