zoukankan      html  css  js  c++  java
  • c++:一个辅助类让内存泄漏现原形!

    前言 对于c++而言,如何查找内存泄漏是程序员亘古不变的话题;解决之道可谓花样繁多。因为最近要用到QT写程序,摆在我面前的第一个重要问题是内存防泄漏。如果能找到一个简单而行之有效的方法,对后续开发大有裨益。久思终得诀窍,本文就详细介绍我对此问题的应对之策。(文末符完整代码)

    如何判断内存有泄漏

      内存分配和释放对应的操作是new、delete。如何判断内存是否释放干净?其实判断起来非常简单:一个独立的模块整个生存周期内new的个数和delete的个数相等。用伪代码标示如下:

        int newCount = 0;
        int deleteCount = 0;
    
        //new 操作时
        new class();
        newCount++;
    
        //delete 操作时
        delete* objPtr;
        deleteCount++;
    
        //模块结束时
       if(newCount != deleteCount)
      {
           内存有泄漏
      }

    如果对所有的new和delete操作,加上如上几行代码,就能发现是否有内存泄漏问题。如果采用上面方法解决问题,手段太low了。

    我们的方法有如下特点:

    1 使用起来超级简单,不增加开发难度。

    2 发生内存泄漏时,能定位到具体是哪个类。

    托管new delete 操作符

      要跟踪所有的new、delete操作,最简单的办法就是托管new、delete。不直接调用系统的操作符,而是用我们自己写的函数处理。在我们的函数内部,则别有洞天; 对new和delete的跟踪和记录就为我所欲也。托管new和delete需用到模板函数,代码如下:

    class MemManage
    {
        //单实例模式
    private:
        static MemManage* _instance_ptr;
    public:
        static MemManage* instance()
        {
            if (_instance_ptr == nullptr)
            {
                _instance_ptr = new MemManage();
            }
            return _instance_ptr;
        }
    
    public:
        MemManage();
    
        //new操作 构造函数没有参数
        template <typename  T>
        T* New()
        {
            ShowOperationMessage<T>(true);
            return new T();
        };
    
        //new操作 构造函数有1个参数
        template <typename  T, typename TParam1>
        T* New(TParam1 param)
        {
            ShowOperationMessage<T>(true);
            return new T(param);
        };
    
        //new操作 构造函数有2个参数
        template <typename  T, typename TParam1, typename TParam2>
        T* New(TParam1 param1, TParam2 param2)
        {
            ShowOperationMessage<T>(true);
            return new T(param1, param2);
        };
    
        //delete 操作
        template <typename  T>
        void  Delete(T t)
        {
            if (t == nullptr)
                return;
    
            ShowOperationMessage<T>(false);
            delete t;
        };
    
        //记录new delete
        template <typename  T>
        void ShowOperationMessage(bool isNew)
        {
            //操作符对应的类名称
            const type_info& nInfo = typeid(T);
            QString className = nInfo.name();
    
            if (isNew)
            {
                _newCount++;
            }
            else
            {
                _deleteCount++;
            }
    
            if (!_showDetailMessage)
            {
                return;
            }
    
            if (isNew)
            {
                qDebug() << "*New" << className << ":" << _newCount << ":" << _deleteCount;
            }
            else
            {
                qDebug() << "Delete" << className << ":" << _newCount << ":" << _deleteCount;
            }
        }
    }

    如何使用辅助类

       使用起来很简单,示例代码如下:

    //*****new和delete使用伪代码
    
    //new操作,需根据构造函数的参数个数调用对应的函数
    //构造函数 没有参数
    QFile* file = MemManage::instance()->New<QFile>();
    
    //构造函数 有1个参数
    QFile* file = MemManage::instance()->New<QFile, QString>("filename");
    
    //构造函数 有2个参数
    QFile* file = MemManage::instance()->New<QFile, QString,bool>("filename",true);
    
    //delete 只有一种形式
    MemManage::instance()->Delete(file);

     一个模块调用周期结束 调用下列代码,查看是否有内存泄漏:

      void ShowNewDelete(bool isShowDetail)
        {
            int leftNew = _newCount - _deleteCount;
            qDebug() << "***********************";
            qDebug() << "total New:" << _newCount << " Delete:" << _deleteCount << " leftNew:" << leftNew;
        }
    
        MemManage::instance()->ShowNewDelete(true);
        //debug输出如下,如果leftNew为0,则没内存泄漏
        total New : 166  Delete : 6  leftNew : 160

    进一步定位内存泄漏问题

      通过判断new和delete的个数是否相等,只是知道了是否有内存泄漏;进一步定位问题,才能方便我们解决问题。如果能定位到操作哪一个类时,发生了内存泄漏,则问题范围就大大缩小。我们可以按类名,记录new和delete操作个数,c++获取类名函数如下:

    const type_info &nInfo = typeid(T);
    QString className = nInfo.name();

    建立一个map表,记录类名对应的操作信息:

    //每个类 统计的信息
    class MemObjInfo
    {
    public:
        int NewCount = 0;
        int DeletCount = 0;
        QString ClassName;
    };
    
    //map对照表
    QMap<QString, MemObjInfo*> _mapMemObjCount;
    
    //按类名统计
    void AddCount(QString& className, bool isNew)
    {
        QMap<QString, MemObjInfo*>::ConstIterator i = _mapMemObjCount.find(className);
        if (i == _mapMemObjCount.constEnd())
        {
            MemObjInfo* info = new MemObjInfo();
            info->ClassName = className;
            if (isNew)
            {
                info->NewCount++;
            }
            else
            {
                info->DeletCount++;
            }
            _mapMemObjCount.insert(className, info);
        }
        else
        {
            MemObjInfo* info = i.value();
            if (isNew)
            {
                info->NewCount++;
            }
            else
            {
                info->DeletCount++;
            }
        }
    }

    如果有内存泄漏 则会输出如下信息:

    如上图,对5个类的操作发送了内存泄漏。比如我们知道了类OfdDocumentPageAttr发生内存泄漏,就很容易定位问题了。

    辅助类完整代码:

    #ifndef MEMMANAGE_H
    #define MEMMANAGE_H
    
    #include <QDebug>
    #include <QList>
    #include <QMutex>
    
    class LockRealse
    {
    public:
        LockRealse(QMutex* mutex)
        {
            _mutex = mutex;
            _mutex->lock();
        }
    
        ~LockRealse()
        {
            _mutex->unlock();
        }
    
    private:
        QMutex* _mutex;
    };
    
    class MemObjInfo
    {
    public:
        int NewCount = 0;
        int DeletCount = 0;
        QString ClassName;
    };
    
    
    class MemManage
    {
    private:
        static MemManage* _instance_ptr;
    public:
        static MemManage* instance()
        {
            if(_instance_ptr==nullptr)
            {
                _instance_ptr = new MemManage();
            }
            return _instance_ptr;
        }
    
    public:
        MemManage()
        {
            _threadMutex = new QMutex();
            _newCount = 0;
            _deleteCount = 0;
        }
    
        template <typename  T>
        T* New()
        {
            ShowOperationMessage<T>(true);
            return new T();
        };
    
        template <typename  T,typename TParam1>
        T* New(TParam1 param)
        {
            ShowOperationMessage<T>(true);
            return new T(param);
        };
    
        template <typename  T,typename TParam1,typename TParam2>
        T* New(TParam1 param1,TParam2 param2)
        {
            ShowOperationMessage<T>(true);
            return new T(param1,param2);
        };
    
        template <typename  T>
        void  Delete(T t)
        {
            if(t == nullptr)
                return;
    
            ShowOperationMessage<T>(false);
            delete t;
        };
    
        void ShowNewDelete(bool isShowDetail)
        {
            int leftNew = _newCount-_deleteCount;
            qDebug()<<"***********************";
            qDebug()<<"total New:"<<_newCount<<" Delete:"<<_deleteCount<<" leftNew:"<<leftNew;
    
            if(isShowDetail)
            {
                ShowNewDeleteDetail(false);
            }
        }
    
        void SetShowDetail(bool enable)
        {
            _showDetailMessage = enable;
        }
    
        template <typename  T>
        void clearAndDelete(QList<T>& list)
        {
            foreach(T item ,list)
            {
              //  Delete(item);
            }
    
            list.clear();
        };
    
    private:
        template <typename  T>
        void ShowOperationMessage(bool isNew)
        {
            LockRealse lock(_threadMutex);
            const type_info &nInfo = typeid(T);
            QString className = nInfo.name();
            className=TrimClassName(className);
            AddCount(className,isNew);
    
            if(isNew)
            {
                _newCount++;
            }
            else
            {
                _deleteCount++;
            }
    
            if(!_showDetailMessage)
            {
                return ;
            }
    
            if(isNew)
            {
                qDebug()<<"*New"<<className<<":"<<_newCount<<":"<<_deleteCount;
            }
            else
            {
                qDebug()<<"Delete"<<className<<":"<<_newCount<<":"<<_deleteCount;
            }
        }
    
        void AddCount(QString& className,bool isNew)
        {
            QMap<QString,MemObjInfo*>::ConstIterator i =  _mapMemObjCount.find(className);
            if(i == _mapMemObjCount.constEnd())
            {
                MemObjInfo* info = new MemObjInfo();
                info->ClassName = className;
                if(isNew)
                {
                    info->NewCount++;
                }
                else
                {
                    info->DeletCount++;
                }
                _mapMemObjCount.insert(className,info);
            }
            else
            {
                MemObjInfo* info = i.value();
                if(isNew)
                {
                    info->NewCount++;
                }
                else
                {
                    info->DeletCount++;
                }
            }
        }
    
        void ShowNewDeleteDetail(bool isShowAll)
        {
            QMap<QString,MemObjInfo*>::ConstIterator i =  _mapMemObjCount.cbegin();
            for(;i!=_mapMemObjCount.cend();i++)
            {
                MemObjInfo *info = i.value();
                int leftNew =info->NewCount-info->DeletCount ;
                if(leftNew!=0)
                {
                    qDebug()<<"*** obj "<<info->ClassName<<" New:"<<info->NewCount
                           <<" Delete:"<<info->DeletCount
                          <<" Diff:"<<leftNew;
                }
                else
                {
                    if(isShowAll)
                    {
                        qDebug()<<"obj "<<info->ClassName<<" New:"<<info->NewCount
                               <<" Delete:"<<info->DeletCount
                              <<" Diff:"<<leftNew;
                    }
                }
            }
        }
    
        QString TrimClassName(QString& className)
        {
            int n= className.lastIndexOf(" *");
            if(n<0)
                return className.trimmed();
    
            return className.mid(0,n).trimmed();
        }
    
    private:
        QMutex *_threadMutex;
        int _newCount;
        int _deleteCount;
        bool _showDetailMessage =false;
    
        QMap<QString,MemObjInfo*> _mapMemObjCount;
    };
    
    
    #endif // MEMMANAGE_H
    View Code

    后记  解决内存泄漏的方法很多。本文介绍了一种行之有效的方法。开发一个新项目前,就需确定如何跟踪定位内存泄漏,发现问题越早解决起来越简单。程序开发是循序渐进的过程,一个功能模块开发完成后,需及早确定是否有内存泄漏。防微杜渐,步步为营,方能产出高质量的产品。

    专注.NET、VC++。擅长WPF、WinForm、Socket等技术。 技术交流 QQ群(618168615)
  • 相关阅读:
    Luogu P1090 合并果子(优先队列 || priority_queue)
    Luogu P1012 拼数
    hibernate5.2的基本配置
    [bzoj1210][HNOI2004]邮递员【插头dp】
    [bzoj3470]Freda’s Walk【概率与期望dp】
    [bzoj4851][Jsoi2016]位运算【矩阵乘法】【状压dp】
    [bzoj4852][Jsoi2016]炸弹攻击【随机化】
    [bzoj4853][Jsoi2016]飞机调度【最短路】【网络流】
    [bzoj4850][Jsoi2016]灯塔【暴力】
    [bzoj4919][Lydsy1706月赛]大根堆【dp】【启发式合并】【stl】
  • 原文地址:https://www.cnblogs.com/yuanchenhui/p/memleak.html
Copyright © 2011-2022 走看看