关于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
相关产品推荐
相关产品推荐

