xe~:I\|p+cp I?0 内存管理是C++程序员的痛。我的《内存管理变革》系列就是试图讨论更为有效的内存管理方式,以杜绝(或减少)内存泄漏,减轻C++程序员的负担。由于工作忙的缘故,这个系列目前未完,暂停。
jL.TqD2l
M051Testing软件测试网;J'|%H$c^%zvAL"W*_ 这篇短文我想换个方式,讨论一下如何以最快的速度找到内存泄漏。51Testing软件测试网"\L2tttd+b
.JRN
Ne3w
A9D]&]9B'M0 确认是否存在内存泄漏51Testing软件测试网2D[G&L$P.O\J-}.v
.DmQS4z_qB R:Zb0 我们知道,MFC程序如果检测到存在内存泄漏,退出程序的时候会在调试窗口提醒内存泄漏。例如:
%Z%i$c3G$\w'q0class CMyApp : public CWinApp51Testing软件测试网X!zW}/z%S(A!J {51Testing软件测试网-Cp^R5` |3g(l public:51Testing软件测试网.F-GRa1m&a Oc BOOL InitApplication()51Testing软件测试网$j#D5Efr {51Testing软件测试网fKV{g
G/y7@ int* leak = new int[10]; !V? ]P1HNb'f^d0 return TRUE; cc6I"VRf+{0 }51Testing软件测试网V.]5iTAM,A }; |
*R8N[4r*p#U:Joa&r0 产生的内存泄漏报告大体如下:
yn$J/VK0Detected memory leaks!51Testing软件测试网/Hs
L9ksd7_@ Dumping objects ->51Testing软件测试网}9_Ob.q;uBwY c:/work/test.cpp(186) : {52} normal block at 0x003C4410, 40 bytes long. 3Ku1j:dS3K0 Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD51Testing软件测试网"l-~3Hh!Zd Object dump complete. |
51Testing软件测试网 s(?v N0q\l*f 这挺好。问题是,如果我们不喜欢MFC,那么难道就没有办法?或者自己做?
;q(d#_INr0P*]051Testing软件测试网h"?u9G w)S?5p 呵呵,这不需要。其实,MFC也没有自己做。内存泄漏检测的工作是VC++的C运行库做的。也就是说,只要你是VC++程序员,都可以很方便地检测内存泄漏。我们还是给个样例:
]XGq
TW0K8|~+z IVb0#include <crtdbg.h>51Testing软件测试网T'PJUl[&I ~+G,ZO6o`-f0inline void EnableMemLeakCheck()51Testing软件测试网D P$xb"^y!g4Q^ {51Testing软件测试网ui3t` j!qoU _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);51Testing软件测试网!\c.e~r o }51Testing软件测试网`Z7U0\"zG.\"gR[1dM 51Testing软件测试网n E%g/lxTUx5Pvoid main() v+o[ ?"e(w2I2u)yD0{51Testing软件测试网C*e Yr1}jxfA EnableMemLeakCheck();51Testing软件测试网"CJg;Sq int* leak = new int[10];51Testing软件测试网'B1e
{a+wQ-~
W } #t C#Pqu6J o%@m1L0 |
51Testing软件测试网.^p&|ZQt 运行(提醒:不要按Ctrl+F5,按F5),你将发现,产生的内存泄漏报告与MFC类似,但有细节不同,如下:
tZt0w
V.u0Detected memory leaks!51Testing软件测试网G'`3{4X.YLt;E Dumping objects -> T2uya
`|H&^1rW0{52} normal block at 0x003C4410, 40 bytes long. Rfh{1b
I0JNk0 Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD %nU;pKlegl0Object dump complete. |
51Testing软件测试网#|6|T ~%R*@iP 为什么呢?看下面。51Testing软件测试网Kn/`"`&R"_9`qc.|
51Testing软件测试网t:F^b,[H2m 定位内存泄漏由于哪一句话引起的51Testing软件测试网t)a'H~ H
51Testing软件测试网b-aGu-z 你已经发现程序存在内存泄漏。现在的问题是,我们要找泄漏的根源。
K:p'Nh?051Testing软件测试网/g
VsQ%l{ 一般我们首先确定内存泄漏是由于哪一句引起。在MFC中,这一点很容易。你双击内存泄漏报告的文字,或者在Debug窗口中按F4,IDE就帮你定位到申请该内存块的地方。对于上例,也就是这一句:
z)@2g7K:p+^T;W051Testing软件测试网R'L*?o2rR int* leak = new int[10];
hSX ^6e4PBV051Testing软件测试网c3N$NVm5o&z!l.V
X 这多多少少对你分析内存泄漏有点帮助。特别地,如果这个new仅对应一条delete(或者你把delete漏写),这将很快可以确认问题的症结。
wcC0r1aX;H00Kj%M(nW|cc9dr0 我们前面已经看到,不使用MFC的时候,生成的内存泄漏报告与MFC不同,而且你立刻发现按F4不灵。那么难道MFC做了什么手脚?51Testing软件测试网IaK(cI g:cb
Ug9wc^0 其实不是,我们来模拟下MFC做的事情。看下例:
G*M']o5E0D k*t\-V)b{3L051Testing软件测试网ch7wX,M;A0JC
'bv*g|P9Nh\(`&_Y0inline void EnableMemLeakCheck()51Testing软件测试网#Y${j%P$w*h(x(kq { dE1H?5[U,oa0 _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF); }`
x'JZ0}51Testing软件测试网oC b%E~{I;W)n^TF5a m2E+m*T"a0#ifdef _DEBUG Z1Zm4Cy%zNR+l)V0#define new new(_NORMAL_BLOCK, __FILE__, __LINE__) MuL8v3O@pn#~
|-{c0#endif51Testing软件测试网:Jj8^?
l!qU5yE 51Testing软件测试网&W0cz&}2o#Nm ^qpvoid main()
u2X'\+GV9j0{51Testing软件测试网 M\~P(N(i EnableMemLeakCheck(); .t4|$Cd9D{k6A0 int* leak = new int[10];51Testing软件测试网W SXB]3Sc }51Testing软件测试网 U8yYD{ |
NYA*^-|W;A X0 再运行这个样例,你惊喜地发现,现在内存泄漏报告和MFC没有任何分别了。51Testing软件测试网DF(L(R-R(x2ljm\
51Testing软件测试网l
@5iw.j0M 快速找到内存泄漏51Testing软件测试网;@+qc;cT~C\
4zz`+l9?0WkD0 单确定了内存泄漏发生在哪一行,有时候并不足够。特别是同一个new对应有多处释放的情形。在实际的工程中,以下两种情况很典型:51Testing软件测试网7K*Mc*p W+g
51Testing软件测试网'ah!ZE-_4kZ'h ● 创建对象的地方是一个类工厂(ClassFactory)模式。很多甚至全部类实例由同一个new创建。对于此,定位到了new出对象的所在行基本没有多大帮助。
(}*EIR#hYV0W~:GA_JX0 ● COM对象。我们知道COM对象采用Reference Count维护生命周期。也就是说,对象new的地方只有一个,但是Release的地方很多,你要一个个排除。
p#iS.PEv`051Testing软件测试网 tmh S&Q} 那么,有什么好办法,可以迅速定位内存泄漏?51Testing软件测试网6s?vN8cG1a|V4c
Fz/DxrC[0 答:有。
? s^{.@(v0M051Testing软件测试网4q$G]R&h] a$H
z2uw1z 在内存泄漏情况复杂的时候,你可以用以下方法定位内存泄漏。这是我个人认为通用的内存泄漏追踪方法中最有效的手段。51Testing软件测试网1?![\"f0vJn(h"g1}h l
51Testing软件测试网!b
M3{yD1k 我们再回头看看crtdbg生成的内存泄漏报告:
vB@
be$PI?Q0jFrfCG051Testing软件测试网!p#X5|ZxD'V
Detected memory leaks!51Testing软件测试网^-LV&Ec|
Dumping objects ->
E-L|!Q
t#c*P-x;p0c:/work/test.cpp(186) : {52} normal block at 0x003C4410, 40 bytes long.51Testing软件测试网{n@d
V-n:K
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
/c!M!j'^Y0Object dump complete.
&?QP:e4Uh/{0w"}"G-s/A/b0 除了产生该内存泄漏的内存分配语句所在的文件名、行号为,我们注意到有一个比较陌生的信息:{52}。这个整数值代表了什么意思呢?51Testing软件测试网*bVB)G"qJ1S
H7Gi/\?L+o0 其实,它代表了第几次内存分配操作。象这个例子,{52}代表了第52次内存分配操作发生了泄漏。你可能要说,我只new过一次,怎么会是第52次?这很容易理解,其他的内存申请操作在C的初始化过程调用的呗。:)
2n;nn(_j0FM+uy051Testing软件测试网.`m$i#H$b-i6]Cg 有没有可能,我们让程序运行到第52次内存分配操作的时候,自动停下来,进入调试状态?所幸,crtdbg确实提供了这样的函数:即 long _CrtSetBreakAlloc(long nAllocID)。我们加上它:
+W.Mt!z!{0iEP*A0H'@F
`+B#AvO0
%@
y5~8VA7x0F`X8W\O0inline void EnableMemLeakCheck()51Testing软件测试网r4Z-sT%S/{9Sgo {51Testing软件测试网,r*X l3}*?yaVxq8Y _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF); .W+ViF$_q n9KU_"d0}51Testing软件测试网#r0W5\7K]W qt 51Testing软件测试网 @3v:j-QP#ifdef _DEBUG51Testing软件测试网
Au:mW4M*p #define new new(_NORMAL_BLOCK, __FILE__, __LINE__)51Testing软件测试网2j:LR:iA7xI Lb^!o|n #endif51Testing软件测试网[^D8C6G2{(zi d d+h|)l s0X*Z0void main()51Testing软件测试网
uCzA:?i B |L { :t2e*X2k/q2e+q.Z0 EnableMemLeakCheck(); 5Wt9\&?0o0vP-x1c0 _CrtSetBreakAlloc(52); {x3y,bja5~@0 int* leak = new int[10];51Testing软件测试网~:Z/Q}jH }51Testing软件测试网G0zH3e7Tf(m |
51Testing软件测试网"dTL9YgVV 你发现,程序运行到 int* leak = new int[10];
一句时,自动停下来进入调试状态。细细体会一下,你可以发现,这种方式你获得的信息远比在程序退出时获得文件名及行号有价值得多。因为报告泄漏文件名及行
号,你获得的只是静态的信息,然而_CrtSetBreakAlloc则是把整个现场恢复,你可以通过对函数调用栈分析(我发现很多人不习惯看函数调用
栈,如果你属于这种情况,我强烈推荐你去补上这一课,因为它太重要了)以及其他在线调试技巧,来分析产生内存泄漏的原因。通常情况下,这种分析方法可以在
5分钟内找到肇事者。
%a4mRz}G+@(uCh0PYP\(L!a0J2k9C^0 当然,_CrtSetBreakAlloc要求你的程序执行过程是可还原的(多次执行过程的内存分配顺序不会发生变化)。这个假设在多数情况下成立。不过,在多线程的情况下,这一点有时难以保证。51Testing软件测试网9LM4` n,T8D4vY5\{
51Testing软件测试网`R/BC]$z