C++嵌入Python脚本多次执行崩溃 求内存管理修正方案
C++嵌入Python循环执行崩溃的修复方案
核心问题分析
崩溃的根源大概率是引用计数操作错误:要么是对NULL指针调用了Py_DECREF(比如Python脚本执行出错返回NULL后未做检查),要么是误将Python解释器管理的「借用引用」当成「新引用」释放,导致对象被提前销毁,后续访问时触发内存错误。移除Py_DECREF后不崩溃,是因为跳过了错误的内存释放操作,但必然会引发内存泄漏——每次循环都会残留未释放的PyObject实例。
修正步骤与代码示例
下面是针对典型场景的修正代码,核心是添加异常检查和严格遵循Python C API的引用规则:
#include <Python.h> int main() { // 仅初始化一次Python解释器,不要放在循环内 Py_Initialize(); if (!Py_IsInitialized()) { return 1; } // 加载Python模块和函数(假设脚本名为test.py,函数名为run) PyObject* module = PyImport_ImportModule("test"); if (!module) { PyErr_Print(); Py_Finalize(); return 1; } PyObject* func = PyObject_GetAttrString(module, "run"); if (!func || !PyCallable_Check(func)) { PyErr_Print(); Py_DECREF(module); Py_Finalize(); return 1; } // 循环执行Python函数 for (int i = 0; i < 1000; ++i) { // 构造参数(无参则传NULL) PyObject* args = PyTuple_New(0); if (!args) { PyErr_Print(); continue; } // 调用Python函数,返回新引用 PyObject* output = PyObject_CallObject(func, args); Py_DECREF(args); // 参数使用完立即释放 if (!output) { PyErr_Print(); continue; // 出错时跳过后续操作,避免访问NULL } // 转换为字符串,返回新引用 PyObject* str = PyObject_Str(output); if (!str) { PyErr_Print(); Py_DECREF(output); // 出错时必须先释放已获取的对象,避免泄漏 continue; } // 转换为C字符串(注意:PyUnicode_AsUTF8返回的指针由Python管理,不要手动free) const char* c_str = PyUnicode_AsUTF8(str); if (c_str) { // 这里处理字符串,比如打印 printf("第%d次执行结果:%s\n", i+1, c_str); } // 按顺序释放对象:先释放str,再释放output Py_DECREF(str); Py_DECREF(output); } // 清理全局对象:func是借用引用,不需要DECREF;module是新引用,需要释放 Py_DECREF(module); Py_Finalize(); return 0; }
关键注意事项
- 区分新引用与借用引用
- 像
PyObject_CallObject、PyObject_Str、PyImport_ImportModule这类函数返回的是新引用,必须手动调用Py_DECREF释放 - 像
PyObject_GetAttrString、PyTuple_GetItem这类函数返回的是借用引用,不能调用Py_DECREF,除非先调用Py_INCREF将其转为新引用
- 像
- 必做错误检查
任何返回PyObject*的API调用后,都要检查是否为NULL——一旦出错,必须先释放已成功获取的对象,再跳过本次循环,避免内存泄漏和空指针访问 - 解释器初始化规则
Py_Initialize()和Py_Finalize()要放在循环外,仅执行一次,反复初始化/清理会导致额外的内存问题
内容的提问来源于stack exchange,提问作者Mattia
相关产品推荐
相关产品推荐

