Wednesday, December 2, 2009

基本避免Memory Leak的技巧

Description

很多程式初學者比較沒有在注意一些Memory處理上的問題。其實只要平時稍微注意一下,就可以避免掉很多問題,而且也可以讓你的程式更好以及更穩定。
[編輯] Text

首先,是指標變數內容的問題。通常各位在使用指標時,可能習慣不給予初始值。其實這會造成一些問題。所以建議大家宣告指標變數時一律給予初值。若無法在宣告的同時一併配置記憶體,那麼請給予NULL作為初值。例如
TList *myList = NULL;
另外,許多在delete後,大家可能也就不再理會該指標的內容了。其實,這也是會造成一些麻煩。因為記憶體被delete可能只是系統作一個記號在記憶配置表中,表示該記憶體是free的。原來的記憶體內還是有原先的內容。但是你的程式一個不注意,又去把原來的指標拿起來用。如果馬上就出錯的話,這樣還好。偏偏因為原來的內容都還在,所以這個錯誤會延遲產生影響。這種類型的Bug特別難抓!因為,你根本找不到引發問題的所在,如果你有個地方一直發生 Access Violation,可是該地方的程式碼你已經check過十幾次了,還是找不問題,那很可能你就是遇到這樣的問題。以前在寫一些程式時,我就曾經為了這樣的Bug,弄了三天。可見其傷害有多大。

所以,當delete某指標後,其務必將其內容設為NULL。一方面是使用NULL指標,一定是馬上出錯。所以你的Bug一下就會抓到。另外一個理由是如果你不小心重複delete同一個物件時,一樣會發生延遲影響的錯誤。但是delete一個NULL指標,在BCB內是什麼也不做。所以不用擔心會發生問題。

總結以上提供下面幾個比較方便MACRO給大家使用:
SAFENEW
SAFEDELETE
SAFEDELETE_ARRAY
其中,SAFENEW是用來配置記憶體使用。與一般new不同的是,當配置記憶體產生Exception或是配置不出記憶體時,會產生一個訊息視窗警告。並且,將該指標內容設定為 NULL。

而SAFEDELETE再delete前會先檢查該指標是否為NULL。若是就進行delete動作,並且在delete完後將該指標內容設定為NULL。SAFEDELETE_ARRAY也是一樣的作用,只是他是用來刪除陣列指標用的。例如:
TList *myList = NULL;
char *string = NULL ;

SAFENEW(myList, TList());
SAFENEW(string, char[10]);
...

SAFEDELETE(myList);
SAFEDELETE(string);
上面這三個Macro你可在本文最後的程式列表找到。

如果你的配置的物件只是暫時性的,也就是說只用在某個 function之內。強烈建議你使用auto_ptr。因為,使用了auto_ptr你就無須擔心記憶體是否被釋放掉。只要離開該function或是某個scope,記憶體肯定會被釋放。你就不用老是想著要在哪裡釋放記憶體。不用在每個return前面寫上一堆delete動作。如果你的程式是使用 goto的方式或者寫的像下面這樣…..拜託改用auto_ptr吧…
TList *myList = new TList() ;
TButton *btn = new TButton(this);
char *ptr = new char ;

...
if(OpenFileFailed)
{
 delete myList ;
 delete btn ;
 delete ptr ;
 return ;
}

...
if(OtherFailure)
{
 delete myList ;
 delete btn ;
 delete ptr ;
 return ; 
}

...
if(SomeThingTrue)
{
 delete myList ;
 delete btn ;
 delete ptr ;
 return ;
}

delete myList ;
delete btn ;
delete ptr ;
return ;
如果你的程式配置了記憶體可是你也沒有用goto的方式,也沒有寫成向上面那樣,也沒有使用auto_ptr,也沒有用其它技術來釋放記憶體…. 那麼……你該死了……Memory Leak最多的一定就是你…

safepointer.h 列表:
/**
 * @file SAFE pointer access include file.
 * @author Gary W. Lee
 */
#ifndef __SAFEPOINTER_H__
#define __SAFEPOINTER_H__

#ifdef WINDOWS
#define SP_SHOWMSG(msg) MessageBox(NULL, (msg), ,"ERROR", MB_OK)
#else
#define SP_SHOWMSG(msg) fprintf(stderr, "%snn", (msg))
#endif

#define SAFENEW(p,t) try { n
if(((p) = new t)==NULL) n
{ n
 SP_SHOWMSG("Virtual memory exhausted! Program Exit!"); n
} n
} catch (Exception &e) { n
 SP_SHOWMSG(e.Message.c_str()); n
 p = NULL ; n
}/// 讓物件記憶體配置後檢查是否配置成功。

#define SAFEDELETE(p) if((p)) n
 { delete (p); p = NULL; } /// 讓物件被delete後,將其值設為NULL。並且在delet物件之前,先確定該指標不為NULL。

#define SAFEDELETE_ARRAY(p) if((p)) n
 { delete [] (p); p = NULL; } /// 讓物件Array被delete後,將其值設為NULL。並且在delet物件之前,先確定該指標不為NULL。

#endif // end of !__SAFEPOINTER_H__

No comments: