天天看點

c++:一個輔助類讓記憶體洩漏現原形!

對于c++而言,如何查找記憶體洩漏是程式員亘古不變的話題;解決之道可謂花樣繁多。因為最近要用到QT寫程式,擺在我面前的第一個重要問題是記憶體防洩漏。如果能找到一個簡單而行之有效的方法,對後續開發大有裨益。久思終得訣竅,本文就詳細介紹我對此問題的應對之策。(文末符完整代碼)

前言 對于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++;
        }
    }
}      

如果有記憶體洩漏 則會輸出如下資訊:

c++:一個輔助類讓記憶體洩漏現原形!

如上圖,對5個類的操作發送了記憶體洩漏。比如我們知道了類OfdDocumentPageAttr發生記憶體洩漏,就很容易定位問題了。

輔助類完整代碼:

c++:一個輔助類讓記憶體洩漏現原形!
c++:一個輔助類讓記憶體洩漏現原形!
#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

後記  解決記憶體洩漏的方法很多。本文介紹了一種行之有效的方法。開發一個新項目前,就需确定如何跟蹤定位記憶體洩漏,發現問題越早解決起來越簡單。程式開發是循序漸進的過程,一個功能子產品開發完成後,需及早确定是否有記憶體洩漏。防微杜漸,步步為營,方能産出高品質的産品。