最快速度找到内存泄漏

上一篇 / 下一篇  2012-08-07 10:02:54 / 个人分类:杂谈

.`!B'vzzUT0  内存管理是C++程序员的痛。我的《内存管理变革》系列就是试图讨论更为有效的内存管理方式,以杜绝(或减少)内存泄漏,减轻C++程序员的负担。由于工作忙的缘故,这个系列目前未完,暂停。

(i2v9}j/}8w0

U'c"snW0["[3|0e/O0  这篇短文我想换个方式,讨论一下如何以最快的速度找到内存泄漏。51Testing软件测试网 A_J(mi.@{7Q r1]

51Testing软件测试网E*m5w_a

  确认是否存在内存泄漏

Q%OD6B`TQ$} n5V0

+@0pt1gj,g0  我们知道,MFC程序如果检测到存在内存泄漏,退出程序的时候会在调试窗口提醒内存泄漏。例如:

[X.r-dI+\8_G0
class CMyApp : public CWinApp51Testing软件测试网 ?9} Pv[+x
{51Testing软件测试网5[|'k3g;@k3[
public:51Testing软件测试网o&v n6J9B g-T
   BOOL InitApplication()
q S7V,u:i+?N.j N:N!@&J0   {
|OLLx:@ pm]c0       int* leak = new int[10];51Testing软件测试网4e7` T)~iT{&@$J3q/_
       return TRUE;51Testing软件测试网6O#];z2CV Dd'i
   }
\FE"@sY k4M0};
51Testing软件测试网0\#D(@*?;tD%?

  产生的内存泄漏报告大体如下:

XZaHK;l0
Detected memory leaks!51Testing软件测试网1VaA7r/A/EB5v _S2f
Dumping objects ->
g5R R WPsm0c:/work/test.cpp(186) : {52} normal block at 0x003C4410, 40 bytes long.51Testing软件测试网N,W)bl"z5rtT z{
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
P%|7s F8aF5JBR0Object dump complete.

's!v"Jjp,i)O2y |+A0  这挺好。问题是,如果我们不喜欢MFC,那么难道就没有办法?或者自己做?51Testing软件测试网"NmePo+?$B5Io

:K9BG1JP0  呵呵,这不需要。其实,MFC也没有自己做。内存泄漏检测的工作是VC++的C运行库做的。也就是说,只要你是VC++程序员,都可以很方便地检测内存泄漏。我们还是给个样例:51Testing软件测试网%{,a o5Uo-]$t;I

51Testing软件测试网(EO{g K4M&W"t

#include <crtdbg.h>51Testing软件测试网l+|,a T`s c.y

;X*Y}U1B"@z0inline void EnableMemLeakCheck()
#}(Vm b K9Q*a1|0{51Testing软件测试网 c`x9w9}4w6G6J g
   _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);51Testing软件测试网 e.v8wz/K$od5i
}
51Testing软件测试网k4QzXHA#JW

K|EK!ef.D0void main()
m$F rDCFmE0{51Testing软件测试网G#I"GK.s[pmP3Hu
   EnableMemLeakCheck();51Testing软件测试网-O-hZX8p%lV%x.A&_
   int* leak = new int[10];
-S"M(P2j0P(s;QQ0}
51Testing软件测试网`6^(XNgP1^z5}}

B@J:`]"M)T0  运行(提醒:不要按Ctrl+F5,按F5),你将发现,产生的内存泄漏报告与MFC类似,但有细节不同,如下:51Testing软件测试网X*w xJ$We~i.~2Ok

Detected memory leaks!51Testing软件测试网3@lPsV0y
Dumping objects ->
k,Brr#Y t!__0{52} normal block at 0x003C4410, 40 bytes long.
w,Qf$uT.`)g0 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
(Cb T/A p k^ a(U0Object dump complete.
51Testing软件测试网Y3~nVc

  为什么呢?看下面。

Yc3y z0J:t&@0

TT gaO0n0  定位内存泄漏由于哪一句话引起的51Testing软件测试网PMt |)r&l8I*S

wU t6qv(w_B0  你已经发现程序存在内存泄漏。现在的问题是,我们要找泄漏的根源。

2T/T:RM_"k/O#`1f8Q051Testing软件测试网SZfH/ej

  一般我们首先确定内存泄漏是由于哪一句引起。在MFC中,这一点很容易。你双击内存泄漏报告的文字,或者在Debug窗口中按F4,IDE就帮你定位到申请该内存块的地方。对于上例,也就是这一句:

z.i:a F"|051Testing软件测试网,N,}&m0kc

  int* leak = new int[10];51Testing软件测试网'A4I&eJ$D*OS~iO

51Testing软件测试网wi pe~%I%U9Q1qK

  这多多少少对你分析内存泄漏有点帮助。特别地,如果这个new仅对应一条delete(或者你把delete漏写),这将很快可以确认问题的症结。

[2g\t5R$T@n051Testing软件测试网;\W1I } ` `

  我们前面已经看到,不使用MFC的时候,生成的内存泄漏报告与MFC不同,而且你立刻发现按F4不灵。那么难道MFC做了什么手脚?

X)D&Vy-vZ0

9V.xG:u#\i-^0  其实不是,我们来模拟下MFC做的事情。看下例:51Testing软件测试网[9Wf'XMj*N6\$Ul

51Testing软件测试网` GTGT8g2i6Uq

'_bA6Vbw&b4}*o0
51Testing软件测试网L"@@1p.I;q2L

inline void EnableMemLeakCheck()51Testing软件测试网%b3M&OP6kec }X
{
|F%f;rR"RbD-|0   _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
iXiAc5E(X4xy0}
51Testing软件测试网|T XTz k9d

51Testing软件测试网 Cgo`3I*A!ex

#ifdef _DEBUG
6t/L:fkAC6r3A0#define new   new(_NORMAL_BLOCK, __FILE__, __LINE__)51Testing软件测试网bDa;x7W6\C
#endif
51Testing软件测试网)U9q w1c?8T

51Testing软件测试网%wa#g,^/u&v*k%]{X8w/E

void main()51Testing软件测试网3Q'u"@F7a$}z8X"A%F
{
?&]#__.AG0   EnableMemLeakCheck();51Testing软件测试网BU]f$|:e~ {
   int* leak = new int[10];
;@{K.|d iu.t/@0}
51Testing软件测试网U.S8p&RJl{J ]v;ac

51Testing软件测试网{QiL1~*VV

  再运行这个样例,你惊喜地发现,现在内存泄漏报告和MFC没有任何分别了。

Z3n` H_051Testing软件测试网u#a }'U@'RdX

  快速找到内存泄漏51Testing软件测试网FcKm [6zC

51Testing软件测试网(V_eP%N6YI0j

  单确定了内存泄漏发生在哪一行,有时候并不足够。特别是同一个new对应有多处释放的情形。在实际的工程中,以下两种情况很典型:51Testing软件测试网)S[3Hw:s?

51Testing软件测试网3? R tLX8q|

  ● 创建对象的地方是一个类工厂(ClassFactory)模式。很多甚至全部类实例由同一个new创建。对于此,定位到了new出对象的所在行基本没有多大帮助。

ZX2zj8f7`-@ AD?Q4]0

$FQ tM3TQFD0  ● COM对象。我们知道COM对象采用Reference Count维护生命周期。也就是说,对象new的地方只有一个,但是Release的地方很多,你要一个个排除。

c#Z5~ Gt0

w n[R}C?&xh0  那么,有什么好办法,可以迅速定位内存泄漏?51Testing软件测试网7U;b"p(^T#{

51Testing软件测试网x0} {C:D

  答:有。51Testing软件测试网5dC\8b'G

51Testing软件测试网%W!Y,`&~,t1p

  在内存泄漏情况复杂的时候,你可以用以下方法定位内存泄漏。这是我个人认为通用的内存泄漏追踪方法中最有效的手段。

]8oC?6V,U7i0

Q#m'W8KL0  我们再回头看看crtdbg生成的内存泄漏报告:

~nW$W'xy.i0

| f%hm!`z^hvi0

$sT7j*pp/D.Vx0Detected memory leaks!
(b,g I3BmFo0K|;g*I0Dumping objects ->
2@+l0h(v5o d6b/{_0c:/work/test.cpp(186) : {52} normal block at 0x003C4410, 40 bytes long.
G1n!Vfj S~*y0 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
,ZX0A w'uQ-a.^0Object dump complete.
/T qIAIro0

H.f"q^ W,x9I0  除了产生该内存泄漏的内存分配语句所在的文件名、行号为,我们注意到有一个比较陌生的信息:{52}。这个整数值代表了什么意思呢?51Testing软件测试网M7Fm)w[%L)Fp

P#zvVX'G'k'T0  其实,它代表了第几次内存分配操作。象这个例子,{52}代表了第52次内存分配操作发生了泄漏。你可能要说,我只new过一次,怎么会是第52次?这很容易理解,其他的内存申请操作在C的初始化过程调用的呗。:)51Testing软件测试网 a%Y \;\G'Y}+g

51Testing软件测试网IA&tv2yj1E\

  有没有可能,我们让程序运行到第52次内存分配操作的时候,自动停下来,进入调试状态?所幸,crtdbg确实提供了这样的函数:即 long _CrtSetBreakAlloc(long nAllocID)。我们加上它:

m h'@5L,LM1Xv0

/?]lEMp%VJ)N051Testing软件测试网:[;H5j/K4nh*H^6P:r

51Testing软件测试网H$[.Z7W;mz&e

inline void EnableMemLeakCheck()
#e5h`E nocC r(|2D2R0{
b9z0n,e k0   _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
6] L8s)Gnzy*g$n0}
51Testing软件测试网q9B~7d2y2_3n

@szcR4a/z:Px0#ifdef _DEBUG51Testing软件测试网s/j(T9{R9yR:cC/@#z
#define new   new(_NORMAL_BLOCK, __FILE__, __LINE__)51Testing软件测试网)}2b%[4p J$t!Ca
#endif
51Testing软件测试网O ^$BM(|N%@

51Testing软件测试网 Rc.VwJ/KSM

void main()
.c;C)s2I;iQ0Z0E)o0{
b?[Z@6o{!a0   EnableMemLeakCheck();
\0Bt#KG,B0   _CrtSetBreakAlloc(52);51Testing软件测试网7er*rSA1i
   int* leak = new int[10];51Testing软件测试网9V?3g Y1X8^/E1Ik
}

q;[ {+O0rQ0

2| F6Ch[4L0  你发现,程序运行到 int* leak = new int[10]; 一句时,自动停下来进入调试状态。细细体会一下,你可以发现,这种方式你获得的信息远比在程序退出时获得文件名及行号有价值得多。因为报告泄漏文件名及行 号,你获得的只是静态的信息,然而_CrtSetBreakAlloc则是把整个现场恢复,你可以通过对函数调用栈分析(我发现很多人不习惯看函数调用 栈,如果你属于这种情况,我强烈推荐你去补上这一课,因为它太重要了)以及其他在线调试技巧,来分析产生内存泄漏的原因。通常情况下,这种分析方法可以在 5分钟内找到肇事者。51Testing软件测试网'EL\j+D

}Txr%L~'h#n0  当然,_CrtSetBreakAlloc要求你的程序执行过程是可还原的(多次执行过程的内存分配顺序不会发生变化)。这个假设在多数情况下成立。不过,在多线程的情况下,这一点有时难以保证。51Testing软件测试网 L5J \Aa lm'z

51Testing软件测试网%aG1OKK)_)h5]

TAG:

 

评分:0

我来说两句

Open Toolbar