You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python C API引用计数问题引发随机读访问违规求助

问题描述

我有一段嵌入大型原生Python项目的C++代码,会触发多种随机读访问违规。怀疑是引用计数处理存在问题,但无法定位。代码结构如下:

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);后即可正常运行。无法理解问题所在,请问是否是引用计数处理存在问题?


问题分析与修复

你的代码存在多处引用计数错误,正是这些问题导致了随机读访问违规:

1. None对象的引用计数错误

Py_BuildValue("")返回的是全局单例的None对象,Python规定永远不能对None调用Py_DECREF。你在构造函数中给values和incr_values赋值为None,之后在析构函数中调用Py_DECREF,这会错误减少None的引用计数,触发内存访问异常。

修复:直接使用Py_None,并手动增加引用计数(因为要持有该对象的引用):

MyCustomClass(){
    Py_INCREF(Py_None);
    values = Py_None;
    Py_INCREF(Py_None);
    incr_values = Py_None;
}

这样析构函数中的Py_DECREF操作就安全了,因为我们正确持有了None的引用。

2. 新旧值相同时的双重释放问题

当condition为真时,compute_incr_values返回的是values的新引用,随后在Python绑定函数中调用set_incr_values:

  • set_incr_values会先对旧的incr_values调用Py_DECREF,如果旧值恰好和新值(也就是values)是同一个对象,这会直接减少values的引用计数
  • 接着对新值调用Py_INCREF,引用计数恢复
  • 最后外面的Py_DECREF(new_values)又会减少values的引用计数

如果此时values的引用计数降到0,对象会被提前销毁,后续析构函数再调用Py_DECREF(this->values)就会导致重复释放,触发访问违规。

修复:在set_incr_values中先判断新旧值是否相同,相同则跳过操作:

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

这样就能避免新旧值相同时的错误释放操作。

3. 循环中的笔误隐患

循环里的PyTuple_GET_ITEM(input,i)应该是PyTuple_GET_ITEM(this->values,i)吧?如果是笔误,会导致访问无关内存,这也可能触发随机异常,需要修正。

4. GIL持有的问题

Python的引用计数操作不是线程安全的,必须在持有GIL(全局解释器锁)时执行。如果MyCustomClass的析构函数是在GIL未持有时调用的,Py_DECREF会导致线程安全问题。需要确保MyCustomClass的销毁是在GIL持有期间进行,比如在PyMyCustomClass的tp_dealloc函数中先获取GIL,再销毁obj->self。


内容的提问来源于stack exchange,提问作者m3shoot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 16:35:28