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

关于Python C API中Py_INCREF/Py_DECREF使用及内存泄漏的技术问询

你的Python C API内存泄漏问题分析

咱们一步步拆解你给出的代码片段,看看哪些地方的引用计数处理有问题,以及为啥每次调用函数内存都会涨:

先看现有代码里的DECREF是否正确

  • pScript:假设SetPyObjectString是类似PyUnicode_FromString的函数(返回新引用),你之后调用Py_XDECREF(pScript)是对的——因为PyImport_Import不会窃取这个字符串的引用,用完就释放没问题。
  • 但你这段代码里漏掉了几个关键的DECREF,这大概率是内存泄漏的核心原因:
    • pModule:PyImport_Import返回的是新引用的模块对象,不管你用不用完,只要不是NULL,都得在函数结束前调用Py_DECREF(pModule),不然这个模块对象会一直占着内存。
    • 你提到的pFunc:如果是用PyObject_GetAttrString从模块里拿的函数,这也是新引用,用完必须Py_DECREF(pFunc)。
    • pValue:如果是调用PyObject_CallObject得到的返回值,同样是新引用,哪怕你不需要这个值,也得调用Py_DECREF(pValue)释放。
    • pArgs:如果之后你用PyTuple_New创建了参数元组,用完也得Py_XDECREF(pArgs)。

什么时候需要调用Py_INCREF?

一般只有两种情况需要手动加引用计数:

  • 你需要把某个对象存到全局变量、列表/字典这类长期存在的结构里,要确保它不会被意外释放;
  • 你拿到的是借用引用(比如PyList_GetItem返回的元素),但需要在当前作用域之外继续使用它。
    从你给出的代码片段看,都是临时使用的对象,暂时不需要额外调用Py_INCREF。

修复后的代码示例(补全关键DECREF)

假设你完整的函数逻辑是加载模块、调用函数,修复后的版本应该是这样:

PyObject *pScript, *pModule, *pFunc, *pValue;
PyObject *pArgs = NULL;
long ret = 1;

// 初始化Python、设置路径、加载模块
pScript = SetPyObjectString(PYTHON_SCRIPT_NAME);
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('"PYTHON_SCRIPT_PATH"')");
pModule = PyImport_Import(pScript);
Py_XDECREF(pScript); // 正确释放pScript

if (pModule != NULL) {
    // 获取目标函数
    pFunc = PyObject_GetAttrString(pModule, "your_target_func");
    if (pFunc != NULL && PyCallable_Check(pFunc)) {
        // 创建参数元组(这里假设无参数,根据实际情况调整)
        pArgs = PyTuple_New(0);
        pValue = PyObject_CallObject(pFunc, pArgs);
        
        if (pValue != NULL) {
            // 如果需要处理返回值就在这里做,处理完释放
            Py_DECREF(pValue);
        } else {
            // 函数调用失败的处理
            ret = 0;
        }
        Py_XDECREF(pArgs); // 释放参数元组
        Py_DECREF(pFunc); // 释放函数对象
    } else {
        // 获取函数失败的处理
        ret = 0;
    }
    Py_DECREF(pModule); // 关键:必须释放模块对象!
} else {
    // 模块加载失败的处理
    ret = 0;
}

return ret;

额外的泄漏排查技巧

  • 可以用Python的tracemalloc模块来跟踪内存分配,配合你的C函数调用,看看哪些对象没被释放;
  • 用C层面的工具比如Valgrind来跑程序,能精准定位到未释放的内存块;
  • 记住一个原则:只要是从Python C API拿到的新引用,用完必须对应DECREF,只有少数返回借用引用的API(比如PyDict_GetItem)不需要,但也不能随便INCREF后不释放。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 08:56:46