Python C扩展内存泄漏求助:创建Python类实例时出现泄漏
内存泄漏排查:C扩展创建Python Dataclass实例导致泄漏
问题描述
我有一段代码存在内存泄漏,泄漏发生在C代码创建Python类实例的环节。我使用create_n_times函数检测泄漏,该函数会循环创建新的Python实例并解除引用。
代码流程如下:
- Python代码定义dataclass,通过
set_ip_settings_type将其注册到C扩展中; - 调用C扩展的
create_n_times函数,创建并销毁n个该Python dataclass的实例。
Python代码
import c_api from dataclasses import dataclass @dataclass class IpSettings: ip: str port: int dhcp: bool c_api.set_ip_settings_type(IpSettings) c_api.create_n_times(100000)
C++代码(编译为c_api扩展)
#include <Python.h> // ... 其他函数包括"PyInit"函数 extern "C" { PyObject* ip_settings_type = NULL; PyObject* set_ip_settings_type(PyObject* tp) { Py_XDECREF(ip_settings_type); Py_INCREF(tp); ip_settings_type = tp; return Py_None; } PyObject* create_n_times(PyObject* n) { long n_ = PyLong_AsLong(n); for (int i = 0; i < n_ ++i) { PyObject* factory_object = ip_settings_type; PyObject* args = PyTuple_New(3); PyTuple_SetItem(args, 0, PyUnicode_FromString("123.123.123.123")); PyTuple_SetItem(args, 1, PyLong_FromUnsignedLong(1231)); PyTuple_SetItem(args, 2, Py_False); PyObject* obj = PyObject_CallObject(factory_object, args); Py_DECREF(obj); } return Py_None; } }
泄漏原因及修复方案
核心泄漏点
每次循环中创建的args元组未调用Py_DECREF释放。PyTuple_New生成的元组引用计数为1,PyTuple_SetItem会接管元素的引用所有权,但元组本身的引用计数必须手动递减,否则每次循环都会泄漏一个元组对象。
其他问题
- 循环条件语法错误:
for (int i = 0; i < n_ ++i)写法错误,会导致n_每次循环递增,触发无限循环。正确写法为for (int i = 0; i < n_; i++)。 - 缺少错误检查:未校验
PyTuple_New的返回值,若内存分配失败会引发空指针操作。 - 返回Py_None不规范:直接返回
Py_None未增加其引用计数,应使用Py_RETURN_NONE宏自动处理引用计数。
修复后的C++代码
#include <Python.h> // ... 其他函数包括"PyInit"函数 extern "C" { PyObject* ip_settings_type = NULL; PyObject* set_ip_settings_type(PyObject* tp) { Py_XDECREF(ip_settings_type); Py_INCREF(tp); ip_settings_type = tp; Py_RETURN_NONE; } PyObject* create_n_times(PyObject* n) { long n_ = PyLong_AsLong(n); // 修正循环条件 for (int i = 0; i < n_; i++) { PyObject* factory_object = ip_settings_type; // 校验类类型是否已注册 if (!factory_object) { PyErr_SetString(PyExc_RuntimeError, "IpSettings type not registered"); return NULL; } PyObject* args = PyTuple_New(3); if (!args) { // 校验元组创建是否成功 return NULL; } // PyTuple_SetItem会接管元素引用,无需手动DECREF元素 PyTuple_SetItem(args, 0, PyUnicode_FromString("123.123.123.123")); PyTuple_SetItem(args, 1, PyLong_FromUnsignedLong(1231)); PyTuple_SetItem(args, 2, Py_False); PyObject* obj = PyObject_CallObject(factory_object, args); if (obj) { Py_DECREF(obj); } // 释放元组引用 Py_DECREF(args); } Py_RETURN_NONE; } }
额外注意事项
- 除
Py_False、Py_True、Py_None这类Python内置单例外,所有通过Py*_New创建的对象都需手动管理引用计数,不再使用时调用Py_DECREF。 - 所有可能返回NULL的Python C API调用都应增加错误检查,避免后续操作触发崩溃。
- 返回内置对象时优先使用
Py_RETURN_NONE、Py_RETURN_TRUE等宏,确保引用计数正确。
内容的提问来源于stack exchange,提问作者EZLearner
相关产品推荐
相关产品推荐

