2008-12-08 112 views
5

下面的函数需要一个python文件句柄,从文件读取压缩二进制数据,创建一个Python字典并返回它。如果我无休止地循环,它会不断消耗RAM。我的RefCounting有什么问题?为什么我的Python C扩展泄漏内存?

static PyObject* __binParse_getDBHeader(PyObject *self, PyObject *args){ 

PyObject *o; //generic object 
PyObject* pyDB = NULL; //this has to be a py file object 

if (!PyArg_ParseTuple(args, "O", &pyDB)){ 
    return NULL; 
} else { 
    Py_INCREF(pyDB); 
    if (!PyFile_Check(pyDB)){ 
     Py_DECREF(pyDB); 
     PyErr_SetString(PyExc_IOError, "argument 1 must be open file handle"); 
     return NULL; 
    } 
} 

FILE *fhDB = PyFile_AsFile(pyDB); 

long offset = 0; 
DB_HEADER *pdbHeader = malloc(sizeof(DB_HEADER)); 
fseek(fhDB,offset,SEEK_SET); //at the beginning 
fread(pdbHeader, 1, sizeof(DB_HEADER), fhDB); 
if (ferror(fhDB)){ 
    fclose(fhDB); 
    Py_DECREF(pyDB); 
    PyErr_SetString(PyExc_IOError, "failed reading database header"); 
    return NULL; 
} 
Py_DECREF(pyDB); 

PyObject *pyDBHeader = PyDict_New(); 
Py_INCREF(pyDBHeader); 

o=PyInt_FromLong(pdbHeader->version_number); 
PyDict_SetItemString(pyDBHeader, "version", o); 
Py_DECREF(o); 

PyObject *pyTimeList = PyList_New(0); 
Py_INCREF(pyTimeList); 

int i; 
for (i=0; i<NUM_DRAWERS; i++){ 
    //epochs 
    o=PyInt_FromLong(pdbHeader->last_good_test[i]); 
    PyList_Append(pyTimeList, o); 
    Py_DECREF(o); 
} 
PyDict_SetItemString(pyDBHeader, "lastTest", pyTimeList); 
Py_DECREF(pyTimeList); 

o=PyInt_FromLong(pdbHeader->temp); 
PyDict_SetItemString(pyDBHeader, "temp", o); 
Py_DECREF(o); 

free(pdbHeader); 
return (pyDBHeader); 
} 

感谢您抽空看看,

LarsenMTL

回答

16

PyDict_New()返回一个新的基准,检查docsPyDict。所以如果你在创建后立即增加refcount,你有两个引用。当你将其作为结果值返回时,其中一个被转移给调用者,但另一个永远不会消失。您也不需要增加pyTimeList。它是你的,当你创建它。然而,你需要减少它,但是你只需递减一次,所以它也被泄漏了。

您也不需要拨pyDB致电Py_INCREF。这是一个借用的引用,只要你的函数没有返回,它就不会消失,因为它仍然在较低的堆栈帧中被引用。

仅当您想要将引用保存在其他结构的某处时,您需要增加引用计数。

参考API docs

+0

Torsten,谢谢,我刚刚在你的4段中学到了更多,然后我整天早上都在盯着文档。我会检查所有我借用的和返回新的参考。 – Mark 2008-12-08 20:40:28

5

OT:对PyList_Append使用连续调用是一个性能问题。既然你知道你有多少成绩提前获得,你可以使用:

PyObject *pyTimeList = PyList_New(NUM_DRAWERS); 
int i; 
for (i=0; i<NUM_DRAWERS; i++){ 
    o = PyInt_FromLong(pdbHeader->last_good_test[i]); 
    PyList_SET_ITEM(pyTimeList, i, o); 
} 

观察,你可能不调用PyList_SET_ITEM后减少的o引用计数,因为它“窃取”的参考。检查docs

2

我不知道Python-C。但是,使用COM引用计数的经验表明,新创建的引用计数对象的引用计数为。所以你的Py_INCREF(pyDB)在PyArg_ParseTuple(args,“O”,& pyDB)和PyObject * pyDBHeader = PyDict_New()之后;是罪魁祸首。他们的引用计数已经是2.