为什么在 C 语言中返回 Py_None 前需要执行 Py_INCREF(Py_None)?

30

为什么在C中返回Py_None之前需要执行Py_INCREF(Py_None)?

Py_INCREF(Py_None);
return Py_None;
如果省略 Py_INCREF(Py_None),会发生什么?
2个回答

29
缺少 Py_INCREF 会导致对 Py_None 的引用计数不正确,这可能会导致解释器释放 Py_None。由于 Py_NoneObjects/object.c 文件中静态分配:
PyObject _Py_NoneStruct = {
  _PyObject_EXTRA_INIT
  1, &PyNone_Type
};

而在Include/object.h中有以下定义:

#define Py_None (&_Py_NoneStruct)

那么会发生的情况是,解释器将崩溃并显示致命错误:

Fatal Python error: deallocating None

Objects/object.c中的none_dealloc函数生成:
/* ARGUSED */
static void
none_dealloc(PyObject* ignore)
{
    /* This should never get called, but we also don't want to SEGV if
     * we accidentally decref None out of existence.
     */
    Py_FatalError("deallocating None");
}

根据该评论所述,如果NoneType没有自己的释放函数,那么在堆栈上进行free调用时会导致段错误。您可以复制tutorial中的示例,将Py_DECREF(Py_None)调用添加到Noddy_name函数中,构建扩展并循环调用该方法来测试此问题。
在一般情况下,引用计数为0可能会导致程序以多种不同的方式失败。
特别地,Python可以自由地重用被释放的对象使用的内存,这意味着突然间对一个对象的每个引用都可以成为指向随机对象(或空内存位置)的引用,你可能会看到如下情况:
>>> None   #or whatever object that was deallocated
<ARandomObjectYouNeverSawBefore object at ...>

(这其实 曾经 在我编写C扩展时发生过。由于缺少对Py_INCREF的调用,一些对象会在随机时间变成只读缓冲区)。

在其他情况下,可能会引发不同类型的错误,或者解释器可能会崩溃或段错误。


PyNone 可能是静态分配的(我没有Python源代码可以查找),因此我们会尝试释放静态分配的内存,这将非常有趣...无论如何,这样做没有任何好处。 - Voo
@Voo 我也曾这样想过,结果发现它是静态分配的(在object.c中),这导致了致命错误。无论如何,我的推理是正确的,只是适用于忘记增加通用对象引用计数的更一般情况。 - Bakuriu

26

Py_None实际上只是另一个Python对象,但没有方法。

Python将计算对任何PyObject*的引用。无论是字符串、整数还是None都一样。

如果您不增加引用计数,则Python解释器最终会在其引用计数达到0后丢弃该对象,认为没有指向该对象的指针。这意味着下次尝试使用返回值时,您将跟随指向内存中不保证保存Py_None的位置的指针(错误、奇怪的值、分段故障等)。

有替代方法可以避免记住使用Py_INCREF(Py_None)

return Py_BuildValue("");
或者
Py_RETURN_NONE;

2
感谢提供宏。 - Mad Physicist
如果没有对Py_None进行Py_DECREF,除了教会自己不需要担心Py_None的引用计数之外,还会导致任何错误吗? - Matthew Moisen
@MatthewMoisen:如果在32位Python上连续失败数十亿次,它可能会回滚到负值,并最终从另一个方向达到零(然后不恰当的Py_DECREF将尝试释放它)。在64位Python上,需要更多的未处理DECREF才能遇到问题,但从技术上讲,这是可能的 - ShadowRanger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接