为什么我的Python C扩展会出现内存泄漏?

6
下面的函数接收一个Python文件句柄,从文件中读取打包的二进制数据,创建Python字典并返回。如果我无限循环调用它,就会持续消耗内存。我的RefCounting出了什么问题?
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
3个回答

17

PyDict_New() 返回一个新的引用,请查看 PyDict 的文档。因此,如果在创建后立即增加引用计数,就会有两个引用。一个是当您将其作为结果值返回时传递给调用者的,但另一个则不会消失。

您也不需要增加对pyTimeList的引用。创建时它已经是你的了。但是,您需要减少引用次数,但是只减少一次,所以它也被泄漏了。

您也不需要对pyDB调用Py_INCREF。它是一个借用的引用,并且只要函数没有返回,它仍然被低层堆栈帧中的某个地方引用着,因此不会消失。

只有在您想将该引用保留在其他结构中时,才需要增加引用计数。

请参阅API文档


Torsten,谢谢,我在你的四段话中学到的比整个早上盯着文档看的还要多。我会检查所有我的借用与返回新引用。 - Mark

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的引用计数,因为它“窃取”了一个引用。请查看文档

3

我不了解Python-C。然而,我的COM引用计数经验表明,新创建的引用计数对象的引用计数为1。因此,在PyArg_ParseTuple(args, "O", &pyDB)和PyObject *pyDBHeader = PyDict_New();之后,你的Py_INCREF(pyDB)是罪魁祸首。它们的引用计数已经是2。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接