Python-C-api、引用计数和读取访问冲突



我有一段c++代码,包含在一个更大的原生python项目中,它会触发各种随机读取访问违规。我怀疑参考人数的处理有问题,但我无法确定。该代码的特点是一个C++类,该类具有封装到Python对象中的2个属性。

typedef struct
{
PyObject_HEAD
MyCustomClass *self;
} PyMyCustomClass;
class MyCustomClass {
public:
PyObject *values;
PyObject *incr_values;
...
}

这两个属性都被元组初始化为None,MyCustomClass具有以下方法:

MyCustomClass(){
values = Py_BuildValue("");
incr_values= Py_BuildValue("");
}
~MyCustomClass(){
Py_DECREF(this->values);
Py_DECREF(this->incr_values);
}
PyObject *get_values() {
Py_INCREF(this->values);
return this->values;
}
int set_incr_values( PyObject *new_values) {
Py_DECREF(this->incr_values);
Py_INCREF(new_values);
this->incr_values = new_values;
return 0;
}
PyObject *compute_incr_values() {
if( condition )
return this->get_values(); //new reference
else { //add 1 to all values
PyObject *one = Py_BuildValue("i", 1);
Py_ssize_clean_t size = PyTuple_GET_SIZE(this->values);
PyObject *new_values = PyTuple_New(size);
for(Py_ssize_t i = 0; i < size; i++ ) {
PyObject *item = PyTuple_GET_ITEM(input,i);
auto add_fct = Py_TYPE(item)->tp_as_number->nb_add;
PyTuple_SET_ITEM(new_values, i, add_fct(item,one) );
}
Py_DECREF(one);
return new_values; //new reference
}
}
static PyObject *compute_incr_values(PyMyCustomClass *obj, PyObject *Py_UNUSED) {
PyObject *new_values = obj->self->compute_incr_values();
obj->self->set_incr_values(new_values);
Py_DECREF(new_values); //Get rid of unused object
Py_RETURN_NONE;
}

所呈现的代码导致Python代码中触发各种随机读取访问违规。但是,如果我在析构函数中删除Py_DECREF(this->values);,并在compute_incr_values方法中删除Py_DECREF(new_values);,那么它就可以工作了。

我不理解这里的问题。引用计数的处理是否存在问题?

我可以看到您的代码至少有两个问题。

您的set_incr_values功能已损坏

int set_incr_values( PyObject *new_values) {
Py_DECREF(this->incr_values);
Py_INCREF(new_values);
this->incr_values = new_values;
return 0;
}

实际上这里有两个问题。首先,如果new_valuesthis->incr_values相同,则它可能失败。其次,Py_DECREF可能导致执行任意代码(请阅读Py_DECREF文档中的红色大警告(。因此,您必须确保self在递减之前处于有效状态。先做作业是最简单的方法。

更好的方法是:

int set_incr_values( PyObject *new_values) {
PyObject *old = this->incr_values;
this->incr_values = new_values;
Py_INCREF(new_values);
Py_DECREF(this->incr_values);
return 0;
}

values不是元组

您不加批判地将values用作compute_incr_values(例如PyTuple_GET_SIZE(中的元组。然而,当您创建values时,您将None分配给它(您知道这一点,因为您在问题中指出了它,尽管我认为"元组初始化"没有任何意义(。

values = Py_BuildValue("");

也没有错误检查。你在评论中说";为了简单起见,我已经删除了错误检查";。这通常是没有帮助的——当阅读没有错误检查的C API代码时,我的第一个假设是,它失败只是因为它们忽略了一些异常。这种假设通常是正确的。

由于没有最小的可复制示例,因此不可能准确地说出代码的错误。然而,在快速浏览的基础上有很多问题,所以我会对其余部分持怀疑态度

最新更新