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

Python C扩展内存泄漏求助:创建Python类实例时出现泄漏

内存泄漏排查:C扩展创建Python Dataclass实例导致泄漏

问题描述

我有一段代码存在内存泄漏,泄漏发生在C代码创建Python类实例的环节。我使用create_n_times函数检测泄漏,该函数会循环创建新的Python实例并解除引用。

代码流程如下:

  1. Python代码定义dataclass,通过set_ip_settings_type将其注册到C扩展中;
  2. 调用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会接管元素的引用所有权,但元组本身的引用计数必须手动递减,否则每次循环都会泄漏一个元组对象。

其他问题

  1. 循环条件语法错误:for (int i = 0; i < n_ ++i)写法错误,会导致n_每次循环递增,触发无限循环。正确写法为for (int i = 0; i < n_; i++)。
  2. 缺少错误检查:未校验PyTuple_New的返回值,若内存分配失败会引发空指针操作。
  3. 返回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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 17:13:16