2008-12-08 111 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.