如何在SWIG中为扩展C结构体建立引用关系避免Python GC崩溃
解决方案:让Room持有House的Python引用以保证GC顺序
要解决Python GC无法保证Room先于House销毁的问题,核心是让每个Room的Python实例持有对应House的引用,这样Python的垃圾回收机制会确保House只有在所有关联的Room都被销毁后,才会被回收。以下是具体的SWIG代码修改方案:
修改example.i中的Room扩展部分
%module example %include "cmalloc.i" %{ #include "objs.h" #include <Python.h> %} %include "objs.h" %extend House { House(const char * address){ struct House * house = create_house(address); return house; } ~House(){ delete_house($self); } } %extend Room { Room(PyObject *house_obj, int sqft){ struct House *house; // 将Python的House对象转换为C指针,验证类型合法性 if (!SWIG_ConvertPtr(house_obj, (void**)&house, SWIGTYPE_p_House, 0)) { struct Room *room = create_room(house, sqft); // 让Room的Python实例持有House的引用,防止House被提前回收 PyObject_SetAttrString($self, "_house_ref", house_obj); Py_INCREF(house_obj); // 增加引用计数,确保House存活至Room销毁 return room; } // 类型错误时抛出Python异常 PyErr_SetString(PyExc_TypeError, "First argument must be a House instance"); return NULL; } ~Room(){ // 先释放Room的C资源 delete_room($self); // 释放持有的House引用,避免内存泄漏 PyObject *house_ref = PyObject_GetAttrString($self, "_house_ref"); if (house_ref) { Py_DECREF(house_ref); } } }
代码说明
构造函数修改:
- 将Room构造函数的第一个参数改为
PyObject* house_obj,直接操作传入的Python House实例,而非仅转换后的C指针。 - 用
SWIG_ConvertPtr验证传入对象为House类型并转换为C指针,用于调用create_room创建Room实例。 - 通过
PyObject_SetAttrString给Room的Python实例添加隐藏属性_house_ref,保存House的Python对象,同时调用Py_INCREF增加引用计数,确保House在Room存在期间不会被GC回收。
- 将Room构造函数的第一个参数改为
析构函数修改:
- 销毁Room的C资源后,获取
_house_ref属性并调用Py_DECREF减少引用计数。当所有Room都释放引用后,House的引用计数降至0,GC才会回收House并调用其析构函数。
- 销毁Room的C资源后,获取
额外注意事项
- 避免双重释放:调用
delete_house时,C库会自动销毁所有关联的Room。若对应的Python Room实例仍存在,其析构函数调用delete_room会触发段错误。可在House的析构函数中遍历Room并标记C对象已失效,或在Room的析构函数中先检查C对象有效性再执行销毁操作。 - 保护内部属性:
_house_ref为内部属性,可通过SWIG的%pythoncode给Room类添加__delattr__方法禁止外部修改,或使用私有命名规范避免误操作。
内容的提问来源于stack exchange,提问作者Martin Belanger
相关产品推荐
相关产品推荐

