評C/C++實戰之記憶體管理 (轉)

worldblog發表於2007-12-09
評C/C++實戰之記憶體管理 (轉)[@more@]

C/C++實戰之管理 
關鍵字:
C++,記憶體分配, 

 貼文時間  2001-11-11 21:23:47

 原作 Paul_Ni 

歡迎大家來到這片大多數員都心有餘悸的雷區。本世紀偉大的比爾·蓋茨曾經失言:

640K ought to be enough for everybody  -- Bill Gates 1981

相信程式設計師們都經常要編寫一些關於記憶體分配和使用的程式,而且都有過那種生不如死的感覺(當然我是指那種程式的感覺了,可能誇張了些!)

常見的記憶體分配和使用錯誤

1)  記憶體的申請和分配並沒有成功,但程式設計師卻使用了它。一些新手經常會犯這種錯誤,他們並不會留意到記憶體沒有分配成功。判斷指標的值是否為NULL可以有效地避免這種錯誤。

2)  記憶體的分配已經成功,但是卻沒有進行初始化就直接使用它了。首先是觀念上的問題,很多人都沒有在使用指標前要初始化這樣的習慣,然而這個習慣卻是很重要的,希望大家一定要強迫自己養成。第二就是主觀地認為自己申請的記憶體的預設值為0,這樣想是沒有什麼道理的,記憶體分配後的值是不確定的。

3)  上面的兩種工作都已經做好了(已經成功申請並初始化完成),但是操作時卻越界了。

4)  申請了記憶體,使用完了卻忘記了釋放,導致記憶體洩露。這樣的錯誤可以形容為一個惡性的腫瘤,它不會馬上要你的命,但是它會慢慢地吞噬你的系統資源,直到你的程式徹底完蛋。

5)  你很小心地釋放了記憶體,但是卻又使用了它。由於程式很複雜或者順序出錯,這樣可能導致出現上面的錯誤。

 

指標---一把偉大的雙刃劍

 

我真的非常佩服發明指標的人,他簡直太偉大了。能使用如此簡潔地方法將複雜的記憶體結構描述的如此清楚,這本身就是一種偉大的成就。但是,指標之於程式設計師如同武器之於士兵,用好了可以威力無比,用不好則害人害己。

我先說說指標和陣列的區別。陣列名對應著一塊記憶體,它的地址、容量在其生命週期中是不可變的,只有陣列內容是可變的。指標可隨時指向任何型別的記憶體,它的特點就是“變”。指標遠比陣列靈活,但也更危險。

陣列名是不能直接進行賦值和比較的。如果你向要將陣列a賦值給陣列b,不能直接用賦值語句b = a ,這樣會令產生錯誤的。必須使用標準的庫strcpy來進行賦值。相同地,要比較a和b的內容是否相同,不能使用普通的邏輯判斷if(b==a),也要應用庫函式strcmp來判斷。

//陣列……

char a[] = “hello”;

char b[100];

strcpy(b, a);  // b = a is wrong

if (strcmp(b, a) == 0)  //if (b == a) is wrong

  cout<

//指標……

int len = strlen(a);

char *p = (char *)malloc(sizeof(char)*(len+1));

strcpy(p, a);

if (strcmp(p, a) == 0)

  cout<

free(p);

在計算記憶體容量的時候有一點是必須要指出的,那就是sizeof計算陣列是計算它的實際的記憶體容量,而計算指標時則永遠都是4個位元組。C++是永遠沒有辦法直到指標所指的記憶體容量,除非在申請時記住它。

 

free和delete如何對付指標?

 

程式設計師都知道它們是用來釋放申請的記憶體的,但是卻很少有人注意到指標本身並沒有發生什麼變化。各位可以在VC中使用單步跟蹤一下,你們會驚奇地發現當指標p被呼叫了free後它的地址值並沒有改變,只是該地址對應的記憶體中原來有意義的值變成了垃圾,“p”卻還是指向的這塊記憶體。記住,一定要第一時間將p的值設為NULL,否則會讓別人以為p是一個有意義的指標而誤使用它(當別人使用該指標時會判斷指標的值是否為NULL,如果不為NULL就會以為它有意義)。

char *p = (char *)malloc(100);

strcpy(p, “hello”);

free(p);  // the address of  “p” is not changed.

….

if (NULL != p)  //it will return TRUE

  strcpy(p, “world”);  //Wrong!!!

下面提兩點,讓大家可以防止上面的情況出現:

1)  指標宣告後要馬上初始化。因為指標出現的預設值是隨機的,所以一定要賦值為NULL,然後再使用。

2)  呼叫了free和delete後一定要將指標賦值為NULL。原因上面已經提過了,就不再贅述了。

 

本文首先分析了使用記憶體會出現的常見錯誤。然後論述了記憶體使用過程中最為關鍵的一環 — 指標的一些平時不為人注意的用法和技巧。這些都是我平時在做工程專案中積累下的,希望能對大家(特別是那些還在記憶體的苦海中掙扎的苦難弟兄們)會有所幫助。有什麼經驗和問題需要交流的,請我。">paulni@citiz.net

 

 


對該文的評論

  sunny2001 ( 2001-11-13 16:22:46 ) 

很好,拋磚引玉,謝謝各位大俠!
 
  Wind_LQ ( 2001-11-12 19:58:34 ) 

建議C初學者仔細看看,在學到一些基本常識的同時也順便提高自己的除蟲能力。
 
  wilaby ( 2001-11-12 17:26:17 ) 

無賴騙點。我還以為什麼高手寫的呢!!!這些基礎教小學生的吧???
 
  andy_show ( 2001-11-12 16:23:22 ) 

相信有許多文章提到過智慧指標的用法,STL的auto_ptr是最好的例子,透過一個Template類實現大部分用new分配的記憶體的自動釋放。另外一個辦法是透過實現一個Reference Wrapper的Template類來實現基於引用計數的資源的自動釋放,不過開銷要比auto_ptr大,但是好處是可以實現對不同型別的系統資源的自動回收(如系統中的記憶體,GDI,等Handle)。關於如何實現這樣的類可以參考一下VC中_bstr_t的類的實現,雖然這個類只是侷限於對於COM的BSTR型別的封裝,不過其中的思想完全可以應用到像Handle那樣的型別中。另為一個比較好的例子是_com_ptr_t的類,應該可以給各位不少啟發。其實只要善用C++的特性,完全可以避免記憶體洩漏。不過,類似陣列越界這樣的問題還是無法避免。
 
  thunder_yu ( 2001-11-12 14:38:36 ) 

呵呵,用了<叫C實戰之記憶體管理更好一些。 

 
  TopCat ( 2001-11-12 12:29:01 ) 

指出你文章中的幾點小錯誤:
 1. 記憶體分配失敗返回NULL只有當你使用malloc函式家族時才正確,如果使用new,現在標準C++的做法是丟擲一個std::bad_alloc型別的異常。
 2. strcpy()函式只對字元指標才有效,因為它的結束條件是判斷(*)+x == ',如果把它用在其它型別的指標中,哼哼……,其實應該用memcpy()。strcmp也用相同的問題。
 3. C++是不推薦使用malloc/free家族的,因為他們無法呼叫類的constructor和destructor,但你的程式碼中從來只出現了malloc/free,這起碼讓你的題目有一半名不副實。
 4. 其實C++中的指標使用方法和注意事項與C中的區別還是非常大的。C++中更多的是考慮多型以及ctor/dtor的問題,你這篇文章可以說完全展現的是C的指標問題。(其實如上所說的陣列問題,C++中可以使用vector和string最大限度的避免指標)。

我的評價: 

在C++中,我從來都不這樣做。而是:

#define SAFE_DELETE(p)  (delete (p),(p)=NULL)

#if _DE

#if defined(new) && defined(MYNEW)
#undef new
#endif

void * _cdecl operator new (size_t nSize,LPCSTR pFileName,D dwLine)
{
1。使用其他記憶體分配函式分配記憶體 
2。做一些操作,記錄記憶體分配情況和分配次數
3。記錄檔案位置
4。小技巧:
_onexit();
怎麼用?查查MSDN,或問問其他人
}

inline void * _cdecl operator new (size_t nSize)
{
同上,但是沒有檔案位置
}

inline void _cdecl operator delete (void * pMem,LPCSTR pFileName,DWORD dwLine)
{
1。使用其他記憶體釋放函式
2。根據記錄的記憶體分配資訊來初步確定記憶體的合法性、越界等並輸出錯誤資訊
3。VC中的小技巧(其他的編譯器就不知道了):
char buff[1024];
sprintf(buff,"%s(%d) : 出錯資訊n",pFileName,dwLine,...);
OutputDebugString(buff);
這樣,可以讓VC直接跳到出錯程式碼處,當然,僅限於除錯執行(F5)並且MFC Trace中設定了輸出除錯資訊。
}

inline void _cdecl operator delete (void * pMem)
{
1和2同上。
3。輸出分配這塊記憶體是第幾次分配的,然後利用 的條件斷點跟蹤出出錯程式碼

#ifdef MYNEW
 #define new MYNEW
#else
 #define MYNEW new(__FILE__,__LINE__)
#endif

#endif //end _DEBUG

透過以上步驟,基本可以在除錯版本杜絕記憶體使用錯誤


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-990420/,如需轉載,請註明出處,否則將追究法律責任。

相關文章