__init__ 中的成员分配和参考计数

Member assignment and ref counting in __init__

我正在为 Python 编写 C 扩展并完成 documentation,我很难理解 __init__ 函数中的成员分配。

因此,在第 2.2 节中,成员分配如下:

if (first) {
    tmp = self->first;
    Py_INCREF(first);
    self->first = first;
    Py_XDECREF(tmp);
}

后面解释说:

Our type doesn’t restrict the type of the first member, so it could be any kind of object. It could have a destructor that causes code to be executed that tries to access the first member; or that destructor could release the Global interpreter Lock and let arbitrary code run in other threads that accesses and modifies our object.

如果我Py_XDECREF它,self->first就会失效。

我理解这样的情况,如果析构函数释放全局解释器锁,就会有一个悬空指针,我的对象 (self) 可能会被修改,等等......一切都会失控。

但为什么析构函数访问它会出现问题?为什么这部分:

It could have a destructor that causes code to be executed that tries to access the first member;

有问题吗?如果 self->first 的析构函数访问自身,没问题。我不在乎,这是它的问题。

希望我说得够清楚了。感谢您的回复。

每当您允许任意 Python 代码到 运行 时,您需要确保您的对象处于有效状态。

当你 DECREF 一个对象时,任意 Python 代码可能是 运行,例如,如果被删除的对象有一个 __del__ 方法(或者如果它是一个 C 扩展方法 a tp_dealloc)。该代码可以做任何事情,例如(如引用文本中所述)访问实例的 first 属性。

例如:

c = Custom("Firstname", "Lastname", 10)

class T:
    def __del__(self):
        print("T", c.first)  # access the first attribute of the "c" variable

c.first = T()
c.__init__(T())
c.__init__(T())
c.__init__(T())

现在,如果您的 C 代码如下所示:

Py_XDECREF(self->first);
Py_INCREF(first);
self->first = first;

当时 T.__del__ 会 运行(由 Py_XDECREF 触发),它会访问 c.first,那时候它是一个无效对象(因为它有0).

的引用计数

在此示例中,意外地没有中断(在我的计算机上),因为内存尚未重新使用。但是,如果它稍微复杂一些,它 通常会终止 Python 进程(在我的计算机上):

c = Custom("Firstname", "Lastname", 10)

class T:
    def __del__(self):
        print("T", c.first)

class U:
    def __init__(self, t):
        self.t = t

    def __del__(self):
        print("U")

c.first = U(T())
c.__init__(U(T()))  # repeated multiple times to make the crash more likely
c.__init__(U(T()))
c.__init__(U(T()))

通过在调用DECREF之前确保对象处于有效状态(不仅仅是在[=26期间),可以避免(并且应该避免)所有这些=] 但无处不在!),或者将其设置为 null:

Py_CLEAR(self->first);  // Sets the field to null before XDECREF is used
Py_INCREF(first);
self->first = first;

或者立即将其替换为 first:

tmp = self->first;
Py_INCREF(first);
self->first = first;
Py_XDECREF(tmp);

或者如果你不想重复使用这种成语,你也可以创建一个这样的函数:

static void replace_field(PyObject **field, PyObject *val)
{
    PyObject *tmp = *field;
    *field = val;
    Py_XDECREF(tmp);
}

将代码简化为:

Py_INCREF(first);
replace_field(&self->first, first);