C/C++应用程序内存泄漏检查统计方案
一、前绪
C/C++程序给某些程序员的几大印象之一就是内存自己管理容易泄漏容易崩,笔者曾经在一个产品中使用C语言开发维护部分模块,只要产品有内存泄漏和崩溃的问题,就被甩锅“我的程序是C#开发的内存都是托管的,C++那边也没有内存(庇护其好友),肯定是C这边的问题”(话说一个十几年的程序员还停留在语言层面不觉得有点low吗),笔者毕业不到一年,听到此语心里一万头草泥马奔腾而过,默默地修改了程序,注意不是修改bug(哈哈),而是把所有malloc和free都替换成了自定义宏MALLOC和FREE,debug版本所有内存分配释放都打了日志,程序结束自动报告类似“Core Memory Leaks: 字节数”,此后内存泄漏的问题再也没人敢甩过来了。语言仅仅是个工具,人心是大道。
二、C程序内存泄漏检测方案参考
C语言应用程序一般使用malloc和free两个函数分配和释放内存,对它们做内存泄漏检测还是很好想到完美方案的。所谓的完美:1)当内存泄漏时能迅速定位到是哪一行代码分配的;2)使用简单与原先无异;3)release时或者不需要调试内存的时候,仍然使用原生态函数,不影响效率。
#ifdef DEBUG_MEMORY
#define MALLOC(size) MallocDebug(__FILE__, __LINE__, size)
#define FREE(p) FreeDebug(__FILE__, __LINE__, p)
#else
#define MALLOC(size) malloc(size)
#define FREE(p) free(p)
#endif #ifdef DEBUG_MEMORY
#define MEM_OP_MALLOC 1
#define MEM_OP_FREE 0 void LogMemory(const char* file, int line, void* p, int operation, size_t size); void* MallocDebug(const char* file, int line, size_t size)
{
void* p = malloc(size);
LogMemory(file, line, p, MEM_OP_MALLOC, size);
return p;
} void FreeDebug(const char* file, int line, void* p)
{
LogMemory(file, line, p, MEM_OP_FREE, );
free(p);
} void LogMemory(const char* file, int line, void* p, int operation, size_t size)
{
//打印日志(malloc/free、指针、文件名、行号、指针、第几次分配的序号),分配序号可以实现类似与crtdbg的CrtSetBreakAlloc函数的功能
//操作为malloc时,向map插入一条记录,增加内存使用大小;
//操作为free时,在map中找到记录并删除,减少内存使用大小。
} void DetectMemoryLeaks()
{
//打印当前内存管理的map中剩余的没有释放的内存指针、文件名、行号、大小、分配序号
} #endif void Program()
{
int *pArray = MALLOC(sizeof(int) * );
FREE(pArray); #ifdef DEBUG_MEMORY
DetectMemoryLeaks();
#endif
}
C语言应用程序中的上述内存泄漏检测方案至此完美收官,记录分配序号,也可以向CrtSetBreakAlloc那样调试内存泄漏哦。
三、C++程序内存泄漏检测方案参考
近期在跟踪C++项目的内存泄漏,项目包含多个工程(1个exe+多个自开发dll+多个第三方dll)。
1.首先考虑的第一个方案是利用crtdbg。踩得第一个坑是记得看下工程配置运行时库选项用debug版本(/MTd或/MDd),否则无效。非MFC程序报不出可疑泄漏内存的文件名及行号,要在整个程序所有使用new的文件中包含"#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)"的宏定义。对于单个工程程序而言调试比较简单方便;对于多个dll尤其是有第三方库时,/MTd配置下要非常小心,/MDd配置要好很多,但实际中使用crtdbg调试还是偶尔会崩在系统底层内存分配的地方,出现的问题不在个人解决能力之内,放弃了。
2.其次的第二个方案,考虑自己重载operator new和operator delete,当然是要重载operator new(size_t size, const char* file, int line)这个版本才能在泄漏时定位到行号。同样也是要所有使用new的文件中包含"#define new new( __FILE__, __LINE__)"的宏定义。问题是虽然可以重载operator delete(void* p, const char* file, int line)这个版本,但是这个版本只会在placement new失败时才会调用,正常时候还是调用的operator delete(void* p)版本,所以还需要重载operator delete(void* p)版本,问题是没有重载的系统内置的operator new(size_t size)版本分配的所有内存也会走用户重载后的operator delete(void* p)版本,不配对,一起把operator new(size_t size)也重载了。
第二个方案的另外一个问题是程序要包含宏"#define new new( __FILE__, __LINE__)",但第三方库头文件中有placement new的用法new(pointer)classA(),项目大一点头文件顺序不好调,编译失败。还有就是这个方案实践中(多dll全部设置的相同的运行时库配置)也在系统底层分配内存的方法崩溃过,也可能是个人在哪里的处理有问题,总之不再考虑前两个方案了,打算在应用层做处理。
3.最后确定在最上层想方案,首先C++不能自定义操作符,否则就能定义一个操作符A* pA = debugnew A(1, 2)了。宏不能有空格只能考虑函数debugnew(A, 1, 2)了。下面上方案。
所有要分配或释放内存的文件中包含DebugMemory.h头文件(伪代码):
//文件名:DebugMemory.h #ifdef DEBUG_MEMORY
#define NEW(T, ...) DebugNew<T>(__FILE__, __LINE__, __VA_ARGS__)
#define DEL(p) DebugDelete(__FILE__, __LINE__, p)
#define NEW_ARRAY(T, size) DebugNewArray<T>(__FILE__, __LINE__, size)
#define DEL_ARRAY(p) DebugDeleteArray(__FILE__, __LINE__, p)
#else
#define NEW(T, ...) new T(__VA_ARGS__)
#define DEL(p) delete(p)
#define NEW_ARRAY(T, size) new T[size]
#define DEL_ARRAY(p) delete[] p
#endif #ifdef DEBUG_MEMORY template<class T, class... Args>
T* DebugNew(const char* file, int line, Args&&... args)
{
T* p = new T(std::forward<Args>(args)...);
//todo:记录操作(new)、指针、文件、行号、分配号
return p;
} template<class T>
void DebugDelete(const char* file, int line, T* p)
{
//todo:记录操作(delete)、指针、文件、行号
delete p;
} template<class T>
T* DebugNewArray(const char* file, int line, size_t size)
{
T* p = new T[size];
//todo:记录操作(new[])、指针、文件、行号、分配号
return p;
} template<class T>
void DebugDeleteArray(const char* file, int line, T* p)
{
//todo:记录操作(delete)、指针、文件、行号
delete[] p;
} void DetectMemoryLeaks()
{
//todo:统计并打印未释放的内存信息
} #endif
使用DebugMemory.h头文件:
//文件名:main.cpp #include "DebugMemory.h" class A
{
public:
A(){}
A(int a, int b):m_a(a), m_b(b){}
private:
int m_a;
int m_b;
} int main()
{
A* pA = NEW(A, , ); //new A(1, 2)
DEL(pA); //delete pA; A* pArray = NEW_ARRAY(A, ); //new A[10]
DEL_ARRAY(pArray); //delete[] pArray #ifdef DEBUG_MEMORY
DetectMemoryLeaks(); //内存泄漏检测
#endif return ;
}
四、方案评价
1.C语言应用程序的内存泄漏解决方案:完美。
2.C++语言应用程序的内存泄漏解决方案
优点:没有改变默认的operator new和operator delete行为,毕竟危险。
优点:实用性通用性强,完全在应用程序员的控制范围内。因为在应用层,不管什么版本都可以检测内存泄漏,不用考虑跨dll调用产生的问题。
不足:写法习惯改变,原来是new A(1,2),要写成NEW(A, 1, 2),如果C++能实现自定义操作符,那么方案就完美了。
C/C++应用程序内存泄漏检查统计方案的更多相关文章
- Windows平台上C++开发内存泄漏检查方法
充分的利用调试工具可以非常方便地避免内存泄漏问题. 这里介绍两种方法,互为补充,第一种是VC编译器提供的方法,第二种是专用的内存泄漏检查工具Memmory Validator.这两种方法的基本原理是一 ...
- valgrind--CPP程序内存泄露检查工具
内存泄漏是c++程序常见的问题了,特别是服务类程序,当系统模块过多或者逻辑复杂后,很难通过代码看出内存泄漏. valgrind是一个开源的,检测c++程序内存泄漏有效工具,编译时加上-g选项可以定位到 ...
- Java虚拟机性能管理神器 - VisualVM(6) 排查JAVA应用程序内存泄漏【转】
Java虚拟机性能管理神器 - VisualVM(6) 排查JAVA应用程序内存泄漏[转] 标签: javajvm内存泄漏监控工具 2015-03-11 18:30 1870人阅读 评论(0) 收藏 ...
- c# 内存泄漏检查心得
系统环境 windows 7 x64 检查工具:ANTS Memory Profiler 7 或者 .NET Memory Profiler 4.0 开发的软件为winform / windows s ...
- C++程序内存泄漏检测方法
一.前言 在Linux平台上有valgrind可以非常方便的帮助我们定位内存泄漏,因为Linux在开发领域的使用场景大多是跑服务器,再加上它的开源属性,相对而言,处理问题容易形成“统一”的标准.而在W ...
- windbg分析net程序内存泄漏问题
1 问题简介 有客户反馈,打了最新补丁后,服务器的内存暴涨,一直降不下来,程序非常卡.在客户的服务器上抓了一个dump文件,开始分析. 分析问题的思路: 1.找到是那些资源占用了大量内存? ...
- BCB:内存泄漏检查工具CodeGuard
一.为什么写这篇东西 自己在使用BCB5写一些程序时需要检查很多东西,例如内存泄漏.资源是否有释放等等,在使用了很多工具后,发觉BCB5本身自带的工具―CodeGuard,非常不错,使用也挺方便的,但 ...
- 【转】Unix下C程序内存泄漏检测工具Valgrind安装与使用
Valgrind是一款用于内存调试.内存泄漏检测以及性能分析的软件开发工具. Valgrind的最初作者是Julian Seward,他于2006年由于在开发Valgrind上的工作获得了第二届Goo ...
- .Net程序内存泄漏解析
一.概要 大概在今年三月份的时候突然被紧急调到另外一个项目组解决线上内存泄漏问题.经过两周的玩命奋战终于解决了这个问题这里把心路历程及思路分享给大家.希望可以帮助到各位或现在正遇到这样事情的小伙伴提供 ...
随机推荐
- BigTable介绍PPT
- Win32 键盘事件 - 击键消息、字符消息、插入符号(光标)
注:以下内容为学习笔记,多数是从书本.资料中得来,只为加深印象,及日后参考.然而本人表达能力较差,写的不好.因非翻译.非转载,只好选原创,但多数乃摘抄,实为惭愧.但若能帮助一二访客,幸甚! 以下内容主 ...
- Math.Round四舍五入说明
Math.Round默认采用的不是四舍五入法, 而是四舍六入的银行家算法, 如何找回四舍五入法? Math.Round默认采用的不是四舍五入法, 而是四舍六入的银行家算法, 也就是四舍六入五考虑,五 ...
- 用MVVM模式开发中遇到的零散问题总结(2)
原文:用MVVM模式开发中遇到的零散问题总结(2) 本节目录: 1.解决动画属性被劫持问题 2.设置页面焦点默认所在对象 3.XAML模拟键盘按键 4.DataGrid数据源绑定到复杂格式(dynam ...
- C# dotnetcore2.0结合Selenium搜索网页
using System; using OpenQA.Selenium; using OpenQA.Selenium.Chrome; namespace ConsoleApp_Selenium { c ...
- Oracle报错:不是单组分组函数
报错:不是单组分组函数 实例:select sum(HWJZ) ,rq from JcChargeInfo 原因: 1.如果程序中使用了分组函数,则有两种情况可以使用: 程序中存在group by, ...
- PHP MYSQL 获取记录总数
$qid = mysql_query(“SELECT count(aid) as total FROM table group by aid “);//你的查询 $res = mysql_fetch_ ...
- 《Microsoft编写优质无错C程序秘诀》提纲
第1章 假想的编译程序1.使用编译程序所有的可选警告设施2.使用lint来查出编译程序漏掉的错误3.如果有单元测试,就进行单元测试第2章 自己设计并使用断言1.既要维护程序的交付版本,又要维护程序的调 ...
- QT 序列化/串行化/对象持久化
本文以一个实例讲解Qt的序列化方法: Qt版本 4.8.0 Qt序列化简介 Qt采用QDataStream来实现序列化,QT针对不同的实例化对象有不同的要求.这里主要分两类,即:QT中原生的数据类型, ...
- OpenDJ Roadmap
Roadmap https://wikis.forgerock.org/confluence/display/OPENDJ/OpenDJ+Roadmap Forum https://forum.for ...