我应该在此Python C扩展代码块中放置Py_INCREF和Py_DECREF在哪里?

7 投票
2 回答
7134 浏览
提问于 2025-04-16 23:06

每次我调用我的函数时,内存使用量都会增加大约10MB,所以我觉得这里可能有内存泄漏的问题。

....
PyObject *pair = PyTuple_New(2), *item = PyList_New(0);

PyTuple_SetItem(pair, 0, PyInt_FromLong(v[j]));

if(v[j] != DISTANCE_MAX && (p[j] || d[0][j])){
  jp=j;
  while(jp!=istart) {
    PyList_Append(item, PyInt_FromLong(jp));
    jp=p[jp];
  }

  PyList_Append(item, PyInt_FromLong(jp));

  PyList_Reverse(item);
}

PyTuple_SetItem(pair, 1, item);

return pair;
....

当我阅读文档时,发现一些调用像

void
bug(PyObject *list)
{
    PyObject *item = PyList_GetItem(list, 0);

    PyList_SetItem(list, 1, PyInt_FromLong(0L));
    PyObject_Print(item, stdout, 0); /* BUG! */
}

需要像这样处理引用计数

void
no_bug(PyObject *list)
{
    PyObject *item = PyList_GetItem(list, 0);

    Py_INCREF(item);
    PyList_SetItem(list, 1, PyInt_FromLong(0L));
    PyObject_Print(item, stdout, 0);
    Py_DECREF(item);
}

那么,我应该把Py_INCREF和Py_DECREF放在哪里呢?

2 个回答

0

当一个对象被创建时,它的引用计数(refcount)会是1,所以在这段代码执行后:

my_item  = PyInt_FromLong(jp)

位于 my_item 的对象的引用计数就是1。

当你把一个项目放进一个容器里时,这个项目的引用计数会增加,这样这个项目就会被保留。所以在这段代码执行后:

PyList_Append(my_list, my_item);

位于 my_item 的对象的引用计数会变成2。

因此,这行代码:

PyList_Append(item, PyInt_FromLong(jp));

会创建一个引用计数为1的对象,并把它存储在列表中,这样对象的引用计数就增加到2了。

11

你用 PyInt_FromLong() 创建的对象,并把它们放到列表里,应该保存在一个本地变量中。

原因在于一些“所有权规则”:PyInt_FromLong() 生成了一个你拥有的引用。当你调用 PyTuple_SetItem() 时,你又失去了这个所有权,因为 PyTuple_SetItem() 会“偷走”这个引用,所以你不需要再担心它了。但是,PyList_Append() 不会这样做,它只是增加了引用计数。为了让这个对象能够被正确地垃圾回收,你需要通过 DECREF 来释放你的所有权。

所以,不要直接写 PyList_Append(item, PyInt_FromLong(jp)),而是应该这样做:

PyObject * jpo = PyInt_FromLong(jp);
// do some error checking here
PyList_Append(item, jpo);
Py_DECREF(jpo);

这样做可以让程序正常运行。

撰写回答