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

传递列表作为参数时Python引用计数验证及内存问题排查

排查C++调用Python时的内存泄漏与崩溃问题

看起来你的核心逻辑是通的,但Python C API的引用管理细节没处理到位,这正是导致崩溃和泄漏的根源。咱们一步步拆解问题:

1. free_intensities_list中的致命错误:误用PyList_GET_ITEM返回的引用

你在free_intensities_list里对PyList_GET_ITEM拿到的元素直接调用Py_DECREF,这是完全错误的。PyList_GET_ITEM返回的是借用的引用——它不会增加对象的引用计数,只是给你一个指向列表内部元素的指针。如果你对这个指针调用Py_DECREF,会直接减少元素的引用计数,可能导致列表还在使用时元素就被提前释放,进而引发崩溃;或者如果其他地方还持有这个元素的引用,就会导致引用计数错乱,引发内存泄漏或野指针。

修正方案:完全不需要手动遍历列表元素调用Py_DECREF,因为列表本身在被Py_DECREF销毁时,会自动处理所有元素的引用计数。你的free_intensities_list应该简化成:

void ProgramLiveProcess::free_intensities_list(PyObject* i_list) {
    Py_DECREF(i_list);
}

2. get_intensities_list中的内存泄漏

你用PyLong_FromLong创建了ta对象,然后PyList_Append把它加入列表——PyList_Append会自动增加ta的引用计数,但你没有对自己创建的ta调用Py_DECREF,这会导致每个元素都泄漏一次引用(因为你创建的引用没有被释放,列表持有另一个引用)。

修正方案:改用PyList_SetItem更高效,它会直接接管你传入的ta的引用,不需要额外的Py_DECREF:

PyObject* ProgramLiveProcess::get_intensities_list(unsigned int intensities[INTENSITIES_DATA_SIZE]) {
    PyObject* tr = PyList_New(INTENSITIES_DATA_SIZE); // 直接创建指定大小的列表更高效
    if (!tr) return nullptr; // 别忘了错误检查!
    for (int i = 0; i < INTENSITIES_DATA_SIZE; i++) {
        PyObject* ta = PyLong_FromLong((long)intensities[i]);
        if (!ta) {
            Py_DECREF(tr); // 出错时要先释放已创建的列表
            return nullptr;
        }
        PyList_SetItem(tr, i, ta); // PyList_SetItem会接管ta的引用,无需手动DECREF
    }
    return tr;
}

如果坚持用PyList_Append,要在Append后加Py_DECREF(ta);来释放你创建的引用。

3. 析构函数中的错误:free(live_process)

live_process是通过PyObject_GetAttrString获取的Python对象指针,它不是用malloc/new分配的C++内存,绝对不能用free释放!你只需要调用Py_DECREF来减少它的引用计数,Python解释器会自动处理内存回收。

修正方案:删除free(live_process);这一行:

ProgramLiveProcess::~ProgramLiveProcess() {
    Py_DECREF(live_process);
    Py_Finalize();
}

4. send_to_program_live_process中的内存泄漏与引用错误

  • 你调用PyObject_CallObject得到的processed_data_tuple是一个新引用,但你没有对它调用Py_DECREF,这会导致每次调用这个函数都泄漏一个元组对象,长期运行会导致内存泄漏。
  • PyTuple_GetItem返回的也是借用引用,你之前对s0p和s0t调用Py_DECREF是错误的,会导致元组内部的元素引用计数被错误减少,可能引发崩溃。

修正方案:

processed_data* ProgramLiveProcess::send_to_program_live_process(unsigned int intensities[INTENSITIES_DATA_SIZE], bool use_sensor_1) {
    // ... 前面的代码 ...

    //Get the data from the function
    PyObject* s0p = PyTuple_GetItem(processed_data_tuple, 0);
    PyObject* s0t = PyTuple_GetItem(processed_data_tuple, 1);
    // 注意:PyTuple_GetItem返回的是借用引用,不需要DECREF,元组释放时会处理

    //Return a struct containing the data
    processed_data* to_return = (processed_data*)malloc(PROCESSED_DATA_STRUCT_SIZE);
    if (to_return == NULL) {
        Py_DECREF(processed_data_tuple); // 出错时别忘了释放元组
        return nullptr;
    }
    to_return->sensor_1_pressure = (float)PyFloat_AS_DOUBLE(s0p);
    to_return->sensor_1_temperature = (float)PyFloat_AS_DOUBLE(s0t);

    //Free python objects and return
    Py_DECREF(processed_data_tuple); // 释放元组的新引用
    // 不需要Py_DECREF(s0p)和Py_DECREF(s0t)
    return to_return;
}

额外建议:添加错误检查

Python C API的很多函数会在出错时返回nullptr,你当前的代码几乎没有错误检查,这会导致出错时后续操作访问空指针,引发崩溃。比如PyImport_ImportModule、PyObject_GetAttrString、PyTuple_Pack、PyObject_CallObject这些函数都需要检查返回值是否为nullptr,如果出错可以用PyErr_Print()打印错误信息,方便调试。

比如在构造函数中:

ProgramLiveProcess::ProgramLiveProcess() : program_live_processing_module(nullptr), live_process(nullptr) { 
    if (!Py_Initialize()) {
        // 初始化失败处理
        return;
    }
    program_live_processing_module = PyImport_ImportModule(MODULE_NAME);
    if (!program_live_processing_module) {
        PyErr_Print();
        Py_Finalize();
        return;
    }
    live_process = PyObject_GetAttrString(program_live_processing_module, "Program_LiveProcess");
    if (!live_process) {
        PyErr_Print();
        Py_DECREF(program_live_processing_module);
        Py_Finalize();
        return;
    }
}

把这些问题都修正后,你的内存泄漏和崩溃问题应该就能解决了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 13:02:51