什么时候去Py_INCREF?

时间:2018-05-14 18:41:15

标签: python memory-management cpython

我正在进行C扩展,我正处于追踪内存泄漏的程度。从阅读Python的文档来看,很难理解何时增加/减少Python对象的引用计数。此外,经过几天花费尝试嵌入Python解释器(为了将扩展编译为独立程序),我不得不放弃这一努力。所以像Valgrind这样的工具在这里很无奈。

到目前为止,通过反复试验,我了解到,例如,Py_DECREF(Py_None)是一件坏事......但这是否适用于任何常数?我不知道。

到目前为止我的主要困惑可以这样列出:

  1. 如果PyWhatever_New()创建的任何内容都不会超过创建它的过程,我是否必须减少引用计数?
  2. 每个Py_INCREF是否需要与Py_DECREF匹配,还是应该有一个/另一个?
  3. 如果对Python程序的调用导致PyObject*,我是否需要增加它以确保我仍然可以(永远)使用它,或者减少它以确保最终它将被垃圾收集,或者都不是?
  4. 通过堆栈或堆上分配的堆栈上的C API创建Python对象吗? (Py_INCREF可能会在堆上重新分配它们。)
  5. 在将C代码创建的Python对象传递给Python代码之前,我是否需要做一些特殊的事情?如果Python代码比创建Python对象的C代码更长呢?
  6. 最后,据我所知,Python同时具有引用计数和垃圾收集器:如果是这种情况,如果我搞砸了引用计数(即没有足够的减量),它有多重要,GC最终会弄清楚如何处理这些对象?

1 个答案:

答案 0 :(得分:3)

Reference Count Details中涵盖了大部分内容,其余内容将在您提出的具体问题的文档中介绍。但是,要把它全部集中在一个地方:

  

Py_DECREF(Py_None)是一件坏事......但是任何常数都是如此吗?

更一般的规则是,在您没有获得新的/被盗的引用,并且没有调用Py_DECREF的任何内容上调用Py_INCREF是一件坏事。由于您永远不会在任何可以作为常量访问的内容上调用Py_INCREF,这意味着您永远不会在其上调用Py_DECREF

  

我是否必须减少由PyWhatever_New()

创建的任何内容的引用计数

是。任何返回"新参考"必须减少。按照惯例,以_New结尾的任何内容都应返回新的引用,但无论如何都应该记录(例如,请参阅PyList_New)。

  

每个Py_INCREF是否需要与Py_DECREF匹配,还是应该有一个/另一个?

您自己代码中的数字可能不一定平衡。 号必须平衡,但Python本身内部会发生增量和减量。例如,任何返回"新引用的内容"已经做了一个公司,而任何"窃取"参考将对其进行决定。

  

通过堆栈或堆上分配的堆栈上的C API创建Python对象吗? (例如,Py_INCREF可能会在堆上重新分配它们。)

无法通过堆栈上的C API创建对象。 C API只有返回指向对象的指针的函数。

大多数这些对象都是在堆上分配的。有些实际上是在静态内存中。

但是你的代码无论如何都不应该关心。你永远不会分配或删除它们;它们会在PySpam_New和类似函数中分配,并在Py_DECREF将它们设置为0时自行释放,因此它们对您来说并不重要。

(除了你可以通过他们的全局名称访问的常量,例如Py_None。那些,你显然知道它们在静态存储中。)

  

在将C代码创建的Python对象传递给Python代码之前,我是否需要做一些特殊的事情?

没有

  

如果Python代码比创建Python对象的C代码更长,该怎么办?

我不确定你的意思是什么?" outlives"这里。当任何对象依赖于其代码时,您的扩展模块将不会被卸载。 (事实上​​,直到至少3.8,你的模块可能永远不会在关闭之前卸载。)

如果您只是指_New' d对象返回的功能,那不是问题。你必须走得太远,才能在堆栈上分配任何Python对象。并且没有办法将诸如C数组对象或C字符串之类的东西传递给Python代码而不将它们转换为Python元组对象,或Python字节或str。在某些情况下,例如,您可以在PyCapsule中存储指向堆栈中某些内容的指针并传递该指针 - 但这与任何C程序中的相同,并且......只需要&#39这样做。

  

最后,据我所知,Python同时具有引用计数和垃圾收集器

垃圾收集器只是一个循环断路器。如果您的对象通过参考周期保持彼此活动,则可以依赖GC。但是,如果您泄漏了对象的引用,GC将永远不会清理它。