需要有关引用计数的指导

Posted

技术标签:

【中文标题】需要有关引用计数的指导【英文标题】:Need guidance regarding reference counting 【发布时间】:2019-11-22 14:40:01 【问题描述】:

我正在寻找似乎来自一个长时间运行的进程的内存泄漏,该进程包含我编写的 C 扩展。我一直在仔细研究代码和 Extensions 文档,我确信它是正确的,但我想确保 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 位于(递归)链的顶部?这是否意味着当整个事情被垃圾收集时,这里创建的 dict 不会得到 DECREF'd?

这里要明确一点:当我在 C 中构建 Python 类型的嵌套数据结构,然后 DECREF 最顶层的实例时,Python 将递归地 DECREF 结构的所有内容,不是吗?

==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)

【问题讨论】:

【参考方案1】:

在一段看似无关的代码中忘记了 PyList_Append(list, item) 之后的 Py_DECREF(item)。 PyList_SetItem() 窃取引用, PyList_Append() 没有。

【讨论】:

以上是关于需要有关引用计数的指导的主要内容,如果未能解决你的问题,请参考以下文章

自动引用计数混淆

引用计数

关于自动引用计数,我需要了解啥?

引用计数器

linux 文件描述符 引用计数(close(fd)只是使fd的引用计数-1)

Python C API 引用计数器