Python 混淆引用所有权:如何正确地解除分配(通过Py_DECREF)对象的对象?

Python 混淆引用所有权:如何正确地解除分配(通过Py_DECREF)对象的对象?,python,c,memory-leaks,cython,Python,C,Memory Leaks,Cython,我正在分析以下代码,这些代码编译和运行正确,但会产生内存泄漏 cfiboheap是Fibonacci堆的C实现,下面的代码是用于cfiboheap的Cython包装器(它的一部分) 我的怀疑是从insert函数开始的。对象数据已在某处创建并传递给函数insert()。由于函数希望将此对象添加到fiboheap,因此会增加其引用计数。但后来呢?所有权归谁?在我看来,C函数fh_insertkey()只是借用了所有权。然后它返回一个需要封装的专有指针,然后由insert()返回。酷。但是我的对象数据

我正在分析以下代码,这些代码编译和运行正确,但会产生内存泄漏

cfiboheap
是Fibonacci堆的C实现,下面的代码是用于
cfiboheap
的Cython包装器(它的一部分)

我的怀疑是从insert函数开始的。对象
数据
已在某处创建并传递给函数
insert()
。由于函数希望将此对象添加到fiboheap,因此会增加其引用计数。但后来呢?所有权归谁?在我看来,C函数
fh_insertkey()
只是借用了所有权。然后它返回一个需要封装的专有指针,然后由
insert()
返回。酷。但是我的对象
数据
及其引用计数?通过创建胶囊,我创建了一个新对象,但我并没有减少
数据的ref计数。这会产生内存泄漏

(请注意,在返回
insert()
之前注释掉
Py\u INCREF
或添加
Py\u DECREF
,会导致分段错误。)

我的问题是:

1) 为什么在
insert()
过程中需要增加
数据的ref计数

2) 为什么在
提取()过程中不必使用
Py_DECREF

3) 更一般地说,在C和Python之间切换时,如何准确地跟踪引用所有权

4) 如何正确释放像FiboHeap这样的对象?我是否应该在
中预防性地使用
Py\u XDECREF

谢谢

cimport cfiboheap
from cpython.pycapsule cimport PyCapsule_New, PyCapsule_GetPointer
from python_ref cimport Py_INCREF, Py_DECREF 

cdef inline object convert_fibheap_el_to_pycapsule(cfiboheap.fibheap_el* element):
    return PyCapsule_New(element, NULL, NULL)

cdef class FiboHeap:

    def __cinit__(FiboHeap self):
        self.treeptr = cfiboheap.fh_makekeyheap()
        if self.treeptr is NULL:
            raise MemoryError()

    def __dealloc__(FiboHeap self):
        if self.treeptr is not NULL:
            cfiboheap.fh_deleteheap(self.treeptr)

    cpdef object insert(FiboHeap self, double key, object data=None):
        Py_INCREF(data)
        cdef cfiboheap.fibheap_el* retValue = cfiboheap.fh_insertkey(self.treeptr, key, <void*>data)
        if retValue is NULL:
            raise MemoryError()

        return convert_fibheap_el_to_pycapsule(retValue)

    cpdef object extract(FiboHeap self):
        cdef void* ret = cfiboheap.fh_extractmin(self.treeptr)
        if ret is NULL:
            raise IndexError("FiboHeap is empty")

        return <object> ret

    cpdef object decrease_key(FiboHeap self,  object element, double newKey):
        cdef void* ret = cfiboheap.fh_replacekey(self.treeptr, convert_pycapsule_to_fibheap_el(element), newKey)
        if ret is NULL:
            raise IndexError("New Key is Bigger")

        return <object> ret 
extract
不是一个peek,而是一个适当的pop,因此它正在删除C fiboheap中的C元素

总之:
数据的ref计数
显然会导致内存泄漏,但为什么呢?如何阻止它

1)有必要增加
insert
中的引用计数,因为其引用计数将在insert结束时自动减少。Cython不知道您正在为以后存储对象。(您可以检查生成的C代码,以查看函数末尾的
DECREF
)。如果使用引用计数为1的对象(即
.insert(SomeObject())
)调用
insert
,则该对象将在insert结束时销毁,而不使用
INCREF

2) 如果在
extract
期间从
cfiboheap
中删除对象,则您应该执行
DECREF
以确认您不再持有该对象。首先将其强制转换为对象(因此您仍然保留对它的引用)



关于胶囊使用的评论:谁拥有他们指向的
fibsheap_el
(什么时候会被破坏)?如果当
cfiboheap
被破坏时它被破坏,那么您就有一个胶囊问题,其中一个无效指针仍然处于活动状态。在某处使用此胶囊可能会导致问题。如果它没有被
cfiboheap
破坏,那么你可能有另一个内存泄漏。

可以找到关于这个内存泄漏的最初(但不同)问题。你为什么要制作一个胶囊?这似乎是无用的和不安全的。另外,
extract
是偷看还是弹出?我在这里发帖是因为你在最初的问题中提到了它-我想我不明白
fiboheap
做得足够好,可以真正回答这个问题。它可以避免在Cython中使用
PyObject*
s和引用计数,这很难做到正确。@user2357112关于
extract
做什么的问题是这里的关键。
extract
是一个流行音乐!因此它会破坏堆中的元素@user2357112代码不是我写的,但我意识到它在我使用它时导致了内存泄漏。我试图阻止内存泄漏,并且更好地理解对象引用,因为这是一种奇怪的用法。我编辑了一点问题。@user2357112为什么不应该有胶囊?C函数
cfiboheap.fh_insertkey
返回一个C指针,之后必须将该指针传递给其他Python函数,例如
reduce_key(…)
。对第1点的回答仍然有点不清楚。C函数
insert
应该只借用元素的引用,那么为什么Cython应该减少引用计数呢?相反,我认为问题与所解释的有关,即指向堆的有效元素的指针可能在后续提取过程中被释放,从而可能使相同位置的后续插入无效。从这个意义上说,当你说Cython不知道我正在为以后存储元素时,你是对的。
cdef dijkstra(Graph G, int start_idx, int end_idx):

    cdef np.ndarray[object, ndim=1] fiboheap_nodes = np.empty([G.num_nodes], dtype=object) # holds all of our FiboHeap Nodes Pointers
    Q = FiboHeap()
    fiboheap_nodes[start_idx] = Q.insert(0, start_idx)
    # Then occasionally:
    Q.insert(...)
    Q.decrease_key(...)
    Q.extract()

    return
   cdef void* ret = cfiboheap.fh_extractmin(self.treeptr) # refcount is 1 here (from the INCREF when it was stored)
   if ret==NULL:
        # ...

   ret_obj = <object>ret
   # reference count should be 2 here - one for being on the heap and one for ret_obj. Casting to object increases the refcount in Cython
   Py_DECREF(ret_obj) # 1 here
   return ret_obj
try:
    while True:
        self.extract()
except IndexError:
    pass # ignore the error - we're done emptying the heap