传递列表作为参数时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

