我正在为 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);