需要有关引用计数的指导
Need guidance regarding reference counting
我正在寻找一个内存泄漏,它似乎来自一个长 运行 进程,其中包含我编写的 C 扩展。我一直在研究代码和扩展文档,我确信它是正确的,但我想确定 PyList 和 PyDict 的引用处理。
从文档中我了解到 PyDict_SetItem() 借用了对键和值的引用,因此我必须在插入后对它们进行 DECREF。 PyList_SetItem() 和 PyTuple_SetItem() 窃取了对插入项的引用,因此我不必 DECREF。正确吗?
正在创建字典:
PyObject *dict = PyDict_New();
if (dict) {
for (i = 0; i < length; ++i) {
PyObject *key, *value;
key = parse_string(ctx); /* returns a PyString */
if (key) {
value = parse_object(ctx); /* returns some PyObject */
if (value) {
PyDict_SetItem(dict, key, value);
Py_DECREF(value); /* correct? */
}
Py_DECREF(key); /* correct? */
}
if (!key || !value) {
Py_DECREF(dict);
dict = NULL;
break;
}
}
}
return dict;
正在创建列表:
PyObject *list = PyList_New(length);
if (list) {
PyObject *item;
for (i = 0; i < length; ++i) {
item = parse_object(ctx); /* returns some PyObject */
if (item) {
PyList_SetItem(list, i, item);
/* No DECREF here */
} else {
Py_DECREF(list);
list = NULL;
break;
}
}
}
return list;
parse_* 函数不需要额外的检查:它们只在最后一行像这样创建对象(例如):
return PyLong_FromLong(...);
如果他们遇到错误,他们不会创建任何对象,而是在函数体的前面设置一个异常:
return PyErr_Format(...);
编辑
这是 valgrind --leak-check=full 的一些输出。显然这是我的代码泄漏内存,但为什么呢?为什么 PyDict_New 位于(递归)链的顶部?这是否意味着当整个事物都被垃圾收集时,此处创建的字典不会被 DECREF?
这里要明确一点:当我在 C 中构建一个 Python 类型的嵌套数据结构,然后 DECREF 最顶层的实例时,Python 将递归地 DECREF 结构的所有内容,won不是吗?
==4357== at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
==4357== by 0x4F20DBC: PyObject_Malloc (in /usr/lib64/libpython3.6m.so.1.0)
==4357== by 0x4FC0F98: _PyObject_GC_Malloc (in /usr/lib64/libpython3.6m.so.1.0)
==4357== by 0x4FC102C: _PyObject_GC_New (in /usr/lib64/libpython3.6m.so.1.0)
==4357== by 0x4F11EC0: PyDict_New (in /usr/lib64/libpython3.6m.so.1.0)
==4357== by 0xE5821BA: parse_dict (parser.c:350)
==4357== by 0xE581987: parse_object (parser.c:675)
==4357== by 0xE5821F0: parse_dict (parser.c:358)
==4357== by 0xE581987: parse_object (parser.c:675)
==4357== by 0xE5823CE: parse (parser.c:727)
在一段看似无关的代码中忘记在 PyList_Append(list, item) 之后添加 Py_DECREF(item)。 PyList_SetItem() 窃取引用,PyList_Append() 不会。
我正在寻找一个内存泄漏,它似乎来自一个长 运行 进程,其中包含我编写的 C 扩展。我一直在研究代码和扩展文档,我确信它是正确的,但我想确定 PyList 和 PyDict 的引用处理。
从文档中我了解到 PyDict_SetItem() 借用了对键和值的引用,因此我必须在插入后对它们进行 DECREF。 PyList_SetItem() 和 PyTuple_SetItem() 窃取了对插入项的引用,因此我不必 DECREF。正确吗?
正在创建字典:
PyObject *dict = PyDict_New();
if (dict) {
for (i = 0; i < length; ++i) {
PyObject *key, *value;
key = parse_string(ctx); /* returns a PyString */
if (key) {
value = parse_object(ctx); /* returns some PyObject */
if (value) {
PyDict_SetItem(dict, key, value);
Py_DECREF(value); /* correct? */
}
Py_DECREF(key); /* correct? */
}
if (!key || !value) {
Py_DECREF(dict);
dict = NULL;
break;
}
}
}
return dict;
正在创建列表:
PyObject *list = PyList_New(length);
if (list) {
PyObject *item;
for (i = 0; i < length; ++i) {
item = parse_object(ctx); /* returns some PyObject */
if (item) {
PyList_SetItem(list, i, item);
/* No DECREF here */
} else {
Py_DECREF(list);
list = NULL;
break;
}
}
}
return list;
parse_* 函数不需要额外的检查:它们只在最后一行像这样创建对象(例如):
return PyLong_FromLong(...);
如果他们遇到错误,他们不会创建任何对象,而是在函数体的前面设置一个异常:
return PyErr_Format(...);
编辑
这是 valgrind --leak-check=full 的一些输出。显然这是我的代码泄漏内存,但为什么呢?为什么 PyDict_New 位于(递归)链的顶部?这是否意味着当整个事物都被垃圾收集时,此处创建的字典不会被 DECREF?
这里要明确一点:当我在 C 中构建一个 Python 类型的嵌套数据结构,然后 DECREF 最顶层的实例时,Python 将递归地 DECREF 结构的所有内容,won不是吗?
==4357== at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
==4357== by 0x4F20DBC: PyObject_Malloc (in /usr/lib64/libpython3.6m.so.1.0)
==4357== by 0x4FC0F98: _PyObject_GC_Malloc (in /usr/lib64/libpython3.6m.so.1.0)
==4357== by 0x4FC102C: _PyObject_GC_New (in /usr/lib64/libpython3.6m.so.1.0)
==4357== by 0x4F11EC0: PyDict_New (in /usr/lib64/libpython3.6m.so.1.0)
==4357== by 0xE5821BA: parse_dict (parser.c:350)
==4357== by 0xE581987: parse_object (parser.c:675)
==4357== by 0xE5821F0: parse_dict (parser.c:358)
==4357== by 0xE581987: parse_object (parser.c:675)
==4357== by 0xE5823CE: parse (parser.c:727)
在一段看似无关的代码中忘记在 PyList_Append(list, item) 之后添加 Py_DECREF(item)。 PyList_SetItem() 窃取引用,PyList_Append() 不会。