前言 对于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

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

c++:一个辅助类让内存泄漏现原形!的更多相关文章

  1. 一个意想不到的Javascript内存泄漏

    原文:http://point.davidglasser.net/2013/06/27/surprising-javascript-memory-leak.html 本周我在Meter的同事追踪到了一 ...

  2. windows 下面的内存泄漏排查.

    内存泄漏排查 一下本人只是简单的介绍一个实用, 如果读者很感兴趣, 可以查阅msdn自己去深入调查相关的API和原理. API 介绍 1. 马上打印泄漏信息:_CrtDumpMemoryLeaks() ...

  3. .NET 垃圾回收与内存泄漏

    > 前言相信大家一定听过,看过甚至遇到过内存泄漏.在 .NET 平台也一定知道有垃圾回收器,它可以让开发人员不必担心内存的释放问题,因为它会自定管理内存.但是在 .NET 平台下进行编程,绝对不 ...

  4. iOS AFNetworking内存泄漏处理方法

    iOS AFN内存泄漏处理方法 细心的你是否也发现AFN的内存泄漏的问题了呢. 在这里给大家提供一个解决AFN内存泄漏的方法. 单例解决AFN内存泄漏 + (AFHTTPSessionManager ...

  5. java内存泄漏的定位与分析

    1.为什么会发生内存泄漏 java 如何检测内在泄漏呢?我们需要一些工具进行检测,并发现内存泄漏问题,不然很容易发生down机问题. 编写java程序最为方便的地方就是我们不需要管理内存的分配和释放, ...

  6. Android studio 分析内存泄漏

    以前用eclipse的时候,我们采用的是DDMS和MAT,不仅使用步骤复杂繁琐,而且要手动排查内存泄漏的位置,操作起来比较麻烦.后来随着Android studio的潮流,我也抛弃了eclipse加入 ...

  7. python 内存泄漏调试

    Python应用程序内存泄漏的调试 Quake Lee quakelee@geekcn.org 新浪网技术(中国)有限公司 Sina Research & Development Python ...

  8. (翻译)什么是Java的永久代(PermGen)内存泄漏

    http://www.codelast.com/?p=7248 转载请注明出处:http://www.codelast.com/ 本文是我对这篇文章的翻译:What is a PermGen leak ...

  9. .NET垃圾回收与内存泄漏

    相信大家一定听过,看过甚至遇到过内存泄漏.在 .NET 平台也一定知道有垃圾回收器,它可以让开发人员不必担心内存的释放问题,因为它会自定管理内存.但是在 .NET 平台下进行编程,绝对不会发生内存泄漏 ...

随机推荐

  1. 【noi 2.6_7113】Charm Bracelet(DP)

    题意:N个饰物,有重量和渴望程度.问在M的重量限制内能达到的最大的渴望度. 解法:经典的01问题,但有一个小技巧值得记住:用if比较大小比调用max函数快了不少,这题有100ms左右. 1 #incl ...

  2. HDU 2897 邂逅明下(巴士变形)

    题意: 给你n个石子,你最少取p个,最多取q个,问谁能赢 题解: 变形版的巴什博弈,当n>=q+1的时候,那么还是以q+1为一组拿走,剩下一个(n%(q+1)),这个时候如果它小于p的话都直接输 ...

  3. [视频] FFMpeg 基本组成和入门示例

    目录 FFmpeg基本组成 编解码工具 ffmpeg.exe ffmpeg.exe的工作流程 播放器 ffplay.exe 多媒体分析器 ffprobe FFmpeg基本组成 AVFormat 封装了 ...

  4. c# xaml (1)

    原文:https://www.wpf-tutorial.com/xaml/what-is-xaml/ vs2017 新建 wpf 项目,在解决方案里会自动创建MainWindow.xaml文件 1.新 ...

  5. Gym 100796B Wet Boxes(思维)题解

    题意:给一个如图坐标系,每个方形都放在下面两个中间,已知一个木块湿了那么他下面所有的都会湿,显然,不能湿两次.问,每次给出一个坐标,把他弄湿,有几个木块从干变成湿了. 思路:我们把坐标系拉直,就变成了 ...

  6. Hacker101 CTF-Micro-CMS v2

    一.打开网站是这个样子 找到一个登录框,存在注入漏洞 3.我们可以这样更改用户名中的输入: admin' or 1=1 -- 4.错误消息显示Invalid Password,因此我们也应该尝试构造一 ...

  7. Adaptive Threshold

    Adaptive Threshold 1. Otsu's Binarization: Using a discriminant analysis to partition the image into ...

  8. Django : Security in Django

    Security in Django https://docs.djangoproject.com/en/1.10/topics/security/ 1 Cross site scripting (X ...

  9. SwiftUI error All In One

    SwiftUI error All In One Instance member xxx cannot be used on type yyy Instance member 'game' canno ...

  10. ESLint & .eslintignore

    ESLint & .eslintignore https://stackoverflow.com/questions/42250257/disable-eslint-rules-for-fol ...