成员分配和参考计数以__init__为单位



我正在为 Python 编写一个 C 扩展并浏览文档,我很难理解__init__函数中的成员赋值。

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

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

解释后来说:

我们的类型不限制第一个成员的类型,因此它可以是任何类型的对象。它可能有一个析构函数,导致执行尝试访问第一个成员的代码;或者析构函数可以释放全局解释器锁,并让任意代码在访问和修改我们对象的其他线程中运行。

如果我Py_XDECREF它,self->first将变得无效。

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

但是为什么析构函数访问它是一个问题?为什么是这部分:

它可能有一个析构函数,导致执行尝试访问第一个成员的代码;

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

希望我足够清楚。感谢您的回复。

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

当您DECREF对象时,可能会运行任意 Python 代码,例如,如果要删除的对象具有__del__方法(或者如果它是 C 扩展方法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之前确保对象处于有效状态(不仅在__init__期间,而且在任何地方!(,都可以避免(并且应该避免(所有这些都可以避免(并且应该避免(,或者将其设置为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);

最新更新