玩心未泯

卡尔斯希普拉斯

C++博客 首页 新随笔 联系 聚合 管理
  19 Posts :: 0 Stories :: 98 Comments :: 0 Trackbacks
        昨天晚上梦见项目中解码部分又出现了内存泄露,今天到公司,小曲果然跟我说LogFileReader中存在内存泄露问题(不过最后的结果是发生泄露的地方和梦里的不一样~:P)。

        内存泄露这个问题说大不大,可也不小。要是有那么点点泄露,你的程序又不要求长期稳定运行,那也没太大关系。不过我一向是以能够长期稳定运行来要求自己的程序的,所以就不得不干掉它,不然迟早要被它把内存给吃干净了。

        VC6的环境中有一个好处,就是会有内存泄露的提示。 形如:
VC6 的内存泄露提示(调试程序,如果有内存泄露会在程序退出的时候显示在Output窗口)Detected memory leaks!
Dumping objects ->
{8057} normal block at 0x014B3220, 14 bytes long.
 Data: <     @  d \R  > 80 00 00 01 81 40 00 90 64 A0 5C 52 00 CD
{7760} normal block at 0x014B3110, 11 bytes long.
 Data: <   \       > F0 B2 CD 5C 9E C0 00 81 E7 80 CD
{7755} normal block at 0x014B2F88, 11 bytes long.
 Data: <     @ 0`  > 80 00 00 01 81 40 00 30 60 E0 CD
{7626} normal block at 0x014B2148, 85 bytes long.
 Data: <#   * O  T      > 23 11 C1 FE 2A 09 4F C9 C5 54 B9 1C A7 E7 A2 EA

Detected memory leaks!和Dumping objects ->我就不解释了,不懂自己查词典去。
后面的每一处内存泄露的格式都是

   {分配申请号} 块类型 at 首地址,大小 bytes long.
   Data:< ASCII码显示的前16字节内容 > 十六进制显示的前16字节内容

        幸运的话,dbg信息中还有关于申请分配动作所在的文件和行号的纪录,那就会在上面的内容之前显示出来。那样的话解决问题就容易多了。问题是如果你像我上面举的例子那样,没有给出文件和行号,那你可就要费点功夫咯。

        我再来介绍一个技巧,上面讲过内存分配申请号和首地址两个很有用的信息,这两个可以用来帮助你设置断点。用地址为条件设置断点,意图在于当分配了一块内存,其首地址刚好为我们设置的条件中的首地址的时候就中断,我们接着跟踪来探查是否存在内存泄露(因为分配、释放的缘故,同一个地址可能会被使用很多次,所以要往下跟踪探查),而以非配申请号为中断条件用意在于发生一个内存分配,请求编号为我们设置的值时,就中断。不过这两招也会失灵,等会再说为什么。
      
        以申请号为中断条件,你需要在程序启动的地方加入一行代码
_crtBreakAlloc = xx;
        或者是
_CrtSetBreakAlloc(xx); 
        其中的xx就是你要设置的申请号了,至于会如何中断,就留给看官自己去试验了。
 
        以地址为条件呢,就繁琐一点,你要先定位到你程序中用于分配的那个函数,比如VC6的MFC中debug的new就在X:\Program Files\Microsoft Visual Studio\VC98\MFC\SRC\AFXMEM.cpp中,找到函数的最后一个}设置条件断点,条件是pResult == 首地址值,怎么设置断点的条件?不用我教了吧,不会就自己去网上查吧。用这个办法还能够设置大小的条件中断。不过呢,有个问题要注意,以new来说,不同的预编译条件会导致它有几条不同路线,其他的分配方式当然也是有的,留着看官自己探索吧。

        下面解释一下这些方法为什么会失效,原因其实很简单,因为每次运行的时候这个申请编号和首地址都几乎是变化的,所以你只有尝试,如果这些值没有变化,那就恭喜你,可能会发现问题了,要是变化了,可能你也就是竹篮打水一场空了。而如果是通过大小来设置条件断点,你可能就能够抓住问题,因为发生泄露的内存大小一般都是不变的,而万一这个大小太普通了,跟踪100次才能找到问题所在也是可能的。:P

        最后,再介绍点有用的,你有没有发现MFC给你生成的cpp文件里面都有这么几行:
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
        这几行有什么用呢?如果发生内存泄露的文件中有这几行,那么,恭喜你,Output里面就会有文件名和行号了~至于为什么,呵呵,还是老话,留给看官研究吧。

        基本上排查内存泄露的方法技巧,我已经知无不言了。可今天咱遇到的问题,却没那么好找,这工程里面基本上代码都是别人写的,而且文件还不少,前几天帮忙她改的解码文件就有差不多10万行的代码量,而且她那个解码库里面还用的是自己的内存分配管理,上次就是查那个解码库中的内存泄露整了我半天时间,昨天晚上那梦不是没理由的,我就担心这里面出问题。不过还好,今天这问题不在这里面。

        追查的过程我就不描述了,最后的原因倒是值得一提,为什么呢?因为虚拟析构函数的问题。NND!今天把老工程里面的一些代码翻出来看,才发现问题大大的有,修改了好几个类和内存泄露相关的问题,相信有些是目前没有暴露出来的,可那提示依然存在,我当时也稀里糊涂的,我在子类的析构函数中释放的地方设置的断点从来没有断下来过,我居然没怀疑这个事情,唉,现在想起来就惭愧。最后我才注意到这个问题,前面搞什么条件中断阿,什么分配申请号中断阿,全在浪费力气。最后注意到的时候,都已经流了不少汗了,这下想到析构函数的virtual问题,子类的析构函数倒是带了“virtual”关键字,找到基类一看,NND,没有构造函数声明,没有析构函数声明,全部是默认的,难怪进不去。果然声明一个空的虚拟析构函数,问题就解决了,提示没有了~555555早知道先把工程的每个cpp文件中头上加上那么一段,让它给我定位出来再查问题了~



posted on 2007-07-05 18:20 SuperPlayeR 阅读(2335) 评论(4)  编辑 收藏 引用 所属分类: C/C++

评论

# re: 回头读读代码,或许有潜伏的小问题你需要纠正——记一次内存泄露问题的排查 2007-07-05 19:52 SmartPtr
博主果然强, 做梦都在工作, 而且还是内存泄露这么具体的事, 而且还那么准。。。。

我们项目中一般用
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

DEBUG_NEW分配的内存会被跟踪, 所以当程序退出的时候我们能知道哪些内存没被释放以及其详细信息。

博主前面讲的几种方法, 如分配申请号, 内存地址, 内存大小都不曾使用过, 但的确有意思, 有启发。  回复  更多评论
  

# re: 回头读读代码,或许有潜伏的小问题你需要纠正——记一次内存泄露问题的排查 2007-07-06 09:20 SuperPlayeR
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
这个我文中也提到过,一般vc自动帮你生成的代码中都有这几行。确实对定位内存泄露很有帮助,不过有时候我们经常会用到一些第三方的类库,有的类库中高人们喜欢自己写内存分配管理的,这个就不一定灵了。我也是因为项目中有用到这样的类库才用其他方法试了半天,最后才发现原来还是自己的项目中的一个老毛病潜伏着~
其实不是我做梦在工作,而是被一些问题弄怕了~特别是有些问题是项目组成员弄出来的,而他自己又没法定位错误的时候,我就难受了~:)  回复  更多评论
  

# re: 回头读读代码,或许有潜伏的小问题你需要纠正——记一次内存泄露问题的排查 2007-07-12 12:46 rushan
感谢博主这么系统详细的介绍了这些方法,很有体会啊。  回复  更多评论
  

# re: 回头读读代码,或许有潜伏的小问题你需要纠正——记一次内存泄露问题的排查 2007-07-19 16:16 hilary0810
此方法仅仅适用于vc最普通的情况,能够从output中得到代码信息;

可以看到内存泄露的一般规律,具体的我就不罗列出来了,毕竟自己总结的东西掌握的才会掌握更好:)

可以根据这个规律,自定义一个宏来申请不同大小的内存,采用逐次逼近代码来找到泄露位置。  回复  更多评论
  


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理