调试程序,发现有内存泄漏

2024-03-19 03:18

本文主要是介绍调试程序,发现有内存泄漏,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

今天调试程序,发现有内存泄漏但是没有提示具体是哪一行,搞得我很头疼。结果在网上搜索了一些资料,经自己实践后整理如下:
 
    第一种:通过'OutPut窗口'定位引发内存泄漏的代码(下面转,我写的没原文好,也懒得写)。
 
我们知道,MFC程序如果检测到存在内存泄漏,退出程序的时候会在调试窗口提醒内存泄漏。例如:
class CMyApp : public CWinApp{public:   BOOL InitApplication()   {       int* leak = new int[10];       return TRUE;   }};产生的内存泄漏报告大体如下:
Detected memory leaks!Dumping objects ->c:/work/test.cpp(186) : {52} normal block at 0x003C4410, 40 bytes long. Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD Object dump complete.这挺好。问题是,如果我们不喜欢MFC,那么难道就没有办法?或者自己做?
呵呵,这不需要。其实,MFC也没有自己做。内存泄漏检测的工作是VC++的C运行库做的。也就是说,只要你是VC++程序员,都可以很方便地检测内存泄漏。我们还是给个样例:
#include <crtdbg.h>
inline void EnableMemLeakCheck(){   _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);}
void main(){   EnableMemLeakCheck();   int* leak = new int[10];} 运行(提醒:不要按Ctrl+F5,按F5),你将发现,产生的内存泄漏报告与MFC类似,但有细节不同,如下:
Detected memory leaks!Dumping objects ->{52} normal block at 0x003C4410, 40 bytes long. Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD Object dump complete.为什么呢?看下面。
 
定位内存泄漏由于哪一句话引起的你已经发现程序存在内存泄漏。现在的问题是,我们要找泄漏的根源。
一般我们首先确定内存泄漏是由于哪一句引起。在MFC中,这一点很容易。你双击内存泄漏报告的文字,或者在Debug窗口中按F4,IDE就帮你定位到申请该内存块的地方。对于上例,也就是这一句:
   int* leak = new int[10];
这多多少少对你分析内存泄漏有点帮助。特别地,如果这个new仅对应一条delete(或者你把delete漏写),这将很快可以确认问题的症结。
我们前面已经看到,不使用MFC的时候,生成的内存泄漏报告与MFC不同,而且你立刻发现按F4不灵。那么难道MFC做了什么手脚?
其实不是,我们来模拟下MFC做的事情。看下例:
inline void EnableMemLeakCheck(){   _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);}
#ifdef _DEBUG#define new   new(_NORMAL_BLOCK, __FILE__, __LINE__)#endif
void main(){   EnableMemLeakCheck();   int* leak = new int[10];}
再运行这个样例,你惊喜地发现,现在内存泄漏报告和MFC没有任何分别了。
 
 
    第二种方法:直接定位指定内存块错误的代码行(下面转)。
 
单确定了内存泄漏发生在哪一行,有时候并不足够。特别是同一个new对应有多处释放的情形。在实际的工程中,以下两种情况很典型:
创建对象的地方是一个类工厂(ClassFactory)模式。很多甚至全部类实例由同一个new创建。对于此,定位到了new出对象的所在行基本没有多大帮助。   COM对象。我们知道COM对象采用Reference Count维护生命周期。也就是说,对象new的地方只有一个,但是Release的地方很多,你要一个个排除。 那么,有什么好办法,可以迅速定位内存泄漏?
答:有。
在内存泄漏情况复杂的时候,你可以用以下方法定位内存泄漏。这是我个人认为通用的内存泄漏追踪方法中最有效的手段。
我们再回头看看crtdbg生成的内存泄漏报告:
Detected memory leaks!Dumping objects ->c:/work/test.cpp(186) : {52} normal block at 0x003C4410, 40 bytes long. Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD Object dump complete. 除了产生该内存泄漏的内存分配语句所在的文件名、行号为,我们注意到有一个比较陌生的信息:{52}。这个整数值代表了什么意思呢?
其实,它代表了第几次内存分配操作。象这个例子,{52}代表了第52次内存分配操作发生了泄漏。你可能要说,我只new过一次,怎么会是第52次?这很容易理解,其他的内存申请操作在C的初始化过程调用的呗。:)
有没有可能,我们让程序运行到第52次内存分配操作的时候,自动停下来,进入调试状态?所幸,crtdbg确实提供了这样的函数:即 long _CrtSetBreakAlloc(long nAllocID)。我们加上它:
inline void EnableMemLeakCheck(){   _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);}
#ifdef _DEBUG#define new   new(_NORMAL_BLOCK, __FILE__, __LINE__)#endif
void main(){   EnableMemLeakCheck();   _CrtSetBreakAlloc(52);   int* leak = new int[10];}你发现,程序运行到 int* leak = new int[10]; 一句时,自动停下来进入调试状态。细细体会一下,你可以发现,这种方式你获得的信息远比在程序退出时获得文件名及行号有价值得多。因为报告泄漏文件名及行号,你获得的只是静态的信息,然而_CrtSetBreakAlloc则是把整个现场恢复,你可以通过对函数调用栈分析(我发现很多人不习惯看函数调用栈,如果你属于这种情况,我强烈推荐你去补上这一课,因为它太重要了)以及其他在线调试技巧,来分析产生内存泄漏的原因。通常情况下,这种分析方法可以在5分钟内找到肇事者。
当然,_CrtSetBreakAlloc要求你的程序执行过程是可还原的(多次执行过程的内存分配顺序不会发生变化)。这个假设在多数情况下成立。不过,在多线程的情况下,这一点有时难以保证。
 
个人心得:我在用这种方法时开始没看懂,后来在MSDN中也找到了这方面相关的信息,后来才会用。我感觉在这方面网上介绍的不够详细,下面我就相对详细地解释一下(为什么用“相对详细”?本人比较懒)。首先说明一下,下面的函数不需要上面所添加的宏定义和'crtdbg.h'头文件,也不需要EnableMemLeakCheck()函数。只需在main函数一开始运行 _CrtSetBreakAlloc(long (4459))函数。其中4459是申请内存的序号(上面有说明),然后F5运行(不需要设断点),然后会出现“Find Source”这个对话框,点击“取消”。然后会出现“User breakpoint called from code at xxxx”的对话框,点击“确定”,会看到一些汇编的代码(不要怕,其实我也看不懂,算然原来学过点汇编),调出堆栈窗口(call stack),在其中的“main() line xxx + xxx bytes”上双击(或它的上一行双击,我的上一行是一个自定义函数,双击后直接定位到我new的地方,定位还是很准的,开始我怀疑,但最后检查果然是这地方没释放)会定位到错误行。
 
第三种:用Ctrl+B来设定,不过现在好像忘了。效果根第二种方法基本一样。
 
有人会问,既然第一种方法定位没问题,为什么还要介绍第二种?其实在实际应用中,某些内存泄漏它没有定位到哪一行的,只有内存块的序号(有可能我用的不太会用),这个时候就需要用第二种方法。

这篇关于调试程序,发现有内存泄漏的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/824608

相关文章

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

SpringCloud之consul服务注册与发现、配置管理、配置持久化方式

《SpringCloud之consul服务注册与发现、配置管理、配置持久化方式》:本文主要介绍SpringCloud之consul服务注册与发现、配置管理、配置持久化方式,具有很好的参考价值,希望... 目录前言一、consul是什么?二、安装运行consul三、使用1、服务发现2、配置管理四、数据持久化总

Redis 内存淘汰策略深度解析(最新推荐)

《Redis内存淘汰策略深度解析(最新推荐)》本文详细探讨了Redis的内存淘汰策略、实现原理、适用场景及最佳实践,介绍了八种内存淘汰策略,包括noeviction、LRU、LFU、TTL、Rand... 目录一、 内存淘汰策略概述二、内存淘汰策略详解2.1 ​noeviction(不淘汰)​2.2 ​LR

Golang基于内存的键值存储缓存库go-cache

《Golang基于内存的键值存储缓存库go-cache》go-cache是一个内存中的key:valuestore/cache库,适用于单机应用程序,本文主要介绍了Golang基于内存的键值存储缓存库... 目录文档安装方法示例1示例2使用注意点优点缺点go-cache 和 Redis 缓存对比1)功能特性

Go使用pprof进行CPU,内存和阻塞情况分析

《Go使用pprof进行CPU,内存和阻塞情况分析》Go语言提供了强大的pprof工具,用于分析CPU、内存、Goroutine阻塞等性能问题,帮助开发者优化程序,提高运行效率,下面我们就来深入了解下... 目录1. pprof 介绍2. 快速上手:启用 pprof3. CPU Profiling:分析 C

golang内存对齐的项目实践

《golang内存对齐的项目实践》本文主要介绍了golang内存对齐的项目实践,内存对齐不仅有助于提高内存访问效率,还确保了与硬件接口的兼容性,是Go语言编程中不可忽视的重要优化手段,下面就来介绍一下... 目录一、结构体中的字段顺序与内存对齐二、内存对齐的原理与规则三、调整结构体字段顺序优化内存对齐四、内

Linux内存泄露的原因排查和解决方案(内存管理方法)

《Linux内存泄露的原因排查和解决方案(内存管理方法)》文章主要介绍了运维团队在Linux处理LB服务内存暴涨、内存报警问题的过程,从发现问题、排查原因到制定解决方案,并从中学习了Linux内存管理... 目录一、问题二、排查过程三、解决方案四、内存管理方法1)linux内存寻址2)Linux分页机制3)

Java循环创建对象内存溢出的解决方法

《Java循环创建对象内存溢出的解决方法》在Java中,如果在循环中不当地创建大量对象而不及时释放内存,很容易导致内存溢出(OutOfMemoryError),所以本文给大家介绍了Java循环创建对象... 目录问题1. 解决方案2. 示例代码2.1 原始版本(可能导致内存溢出)2.2 修改后的版本问题在

大数据小内存排序问题如何巧妙解决

《大数据小内存排序问题如何巧妙解决》文章介绍了大数据小内存排序的三种方法:数据库排序、分治法和位图法,数据库排序简单但速度慢,对设备要求高;分治法高效但实现复杂;位图法可读性差,但存储空间受限... 目录三种方法:方法概要数据库排序(http://www.chinasem.cn对数据库设备要求较高)分治法(常

Redis多种内存淘汰策略及配置技巧分享

《Redis多种内存淘汰策略及配置技巧分享》本文介绍了Redis内存满时的淘汰机制,包括内存淘汰机制的概念,Redis提供的8种淘汰策略(如noeviction、volatile-lru等)及其适用场... 目录前言一、什么是 Redis 的内存淘汰机制?二、Redis 内存淘汰策略1. pythonnoe