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

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;
}

关键注意事项

  1. 区分新引用与借用引用
    • 像PyObject_CallObject、PyObject_Str、PyImport_ImportModule这类函数返回的是新引用,必须手动调用Py_DECREF释放
    • 像PyObject_GetAttrString、PyTuple_GetItem这类函数返回的是借用引用,不能调用Py_DECREF,除非先调用Py_INCREF将其转为新引用
  2. 必做错误检查
    任何返回PyObject*的API调用后,都要检查是否为NULL——一旦出错,必须先释放已成功获取的对象,再跳过本次循环,避免内存泄漏和空指针访问
  3. 解释器初始化规则
    Py_Initialize()和Py_Finalize()要放在循环外,仅执行一次,反复初始化/清理会导致额外的内存问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 10:02:21