Python C API:将PyObjects分配给字典会导致内存泄漏

时间:2017-04-05 15:57:10

标签: python c++ memory-leaks python-c-api

我正在使用Python C API为Python编写C ++包装器。在我的情况下,我必须为Python脚本提供更大量的面向字节的数据。为此,我使用PyByteArray_FromStringAndSize方法生成Python bytearray(https://docs.python.org/2.7/c-api/bytearray.html)。

直接返回此bytearray时,我没有遇到任何问题。然而,当将bytearray添加到Python dict中时,一旦dict被破坏,bytearray中的内存将不会被释放。

这可以通过在将bytearray对象添加到Python dict之后调用bytearray对象上的Py_DECREF来解决。

下面是我的代码的完整工作示例,其中包含返回普通bytearray的方法dummyArrPlain以及在dict中返回bytearray的方法dummyArrInDict。除非调用Py_DECREF(pyData);,否则第二种方法将产生内存泄漏。

我的问题是:为什么Py_DECREF此时需要。直觉上,我预计一旦dict被销毁就应该调用Py_DECREF

此外,我将如下所示的值分配给dict:

PyDict_SetItem(dict, PyString_FromString("i"), PyInt_FromLong(i));

如果没有在创建的字符串上调用Py_DECREF并且长?

,这是否也会产生内存泄漏?

这是我的虚拟C ++包装器:

#include <python2.7/Python.h>

static char module_docstring[] = "This is a module causing a memory leak";

static PyObject *dummyArrPlain(PyObject *self, PyObject *args);
static PyObject *dummyArrInDict(PyObject *self, PyObject *args);

static PyMethodDef module_methods[] = {
    {"dummy_arr_plain", dummyArrPlain, METH_VARARGS, "returns a plain dummy bytearray"},
    {"dummy_arr_in_dict", dummyArrInDict, METH_VARARGS, "returns a dummy bytearray in a dict"},
    {NULL, NULL, 0, NULL}
};

PyMODINIT_FUNC initlibdummy(void)
{
    PyObject *m = Py_InitModule("libdummy", module_methods);
    if (m == NULL)
        return;
}


static PyObject *dummyArrPlain(PyObject *self, PyObject *args)
{
    int len = 10000000;
    char* data = new char[len];
    for(int i=0; i<len; i++) {
        data[i] = 0;
    }

    PyObject * pyData = PyByteArray_FromStringAndSize(data, len);
    delete [] data;

    return pyData;
}


static PyObject *dummyArrInDict(PyObject *self, PyObject *args)
{
    int len = 10000000;
    char* data = new char[len];
    for(int i=0; i<len; i++) {
        data[i] = 0;
    }
    PyObject * pyData = PyByteArray_FromStringAndSize(data, len);
    delete [] data;

    PyObject *dict = PyDict_New();
    PyDict_SetItem(dict, PyString_FromString("data"), pyData);

    // memory leak without Py_DECREF(pyData);

    return dict;
}

使用包装器的虚拟python脚本:

import libdummy
import time

while True:
    a = libdummy.dummy_arr_in_dict()
    time.sleep(0.01)

1 个答案:

答案 0 :(得分:1)

这是[Python 2.0.Docs]: Ownership rules的问题。我将在 Python 2.7.10 上举例说明(相当古老,但我并不认为这种行为已经(显着)改变了。)

PyByteArray_FromStringAndSize bytearrayobject.c 168 )创建一个新对象(使用 PyObject_New ,并分配内存对于缓冲区也是如此。

默认情况下,该对象的引用计数(或更好:任何新创建的对象) 1 (由 _Py_NewReference 设置),因此,当用户在其上或程序退出时调用 del 时,引用计数将减少,当达到0时,将取消分配该对象。

  • 这是返回对象的流程上的行为

  • 但是,在 dummyArrInDict 的情况下, PyDict_SetItem (间接地) pyData的 Py_INCREF (它做其他事情,但只有这在当前情况下是相关的),最后是 2 引用因此内存泄漏

与您使用数据进行的操作基本相同:您为它分配内存,当您不再需要它时,您可以释放它(这是因为您和#39;不归还它,你只能暂时使用它。

注意:使用 X 宏(例如[Python 2.Docs]: Py_XDECREF)更安全,特别是因为您没有测试 NULL 返回的 PyObject s)。

有关详细信息,请查看[Python 2.Docs]: C API Reference