(來源:http://www.cnblogs.com/gaochaooo/archive/2009/09/03/1559764.html
)
C++記憶體配置秘籍—new,malloc,GlobalAlloc詳解
_______只為因記憶體配置而無法入眠的程式員
一。關於記憶體
1、記憶體配置方式
記憶體配置方式有三種:
(1)從靜態儲存地區分配。記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個運行期間都存在
。例如全域變數,static變數。
(2)在棧上建立。在執行函數時,函數內局部變數的儲存單元都可以在棧上建立,函數執行結束時這些存
儲單元自動被釋放。棧記憶體配置運算內建於處理器的指令集中,效率很高,但是分配的記憶體容量有限。
(3) 從堆上分配,亦稱動態記憶體分配。程式在啟動並執行時候用malloc或new申請任意多少的記憶體,程式員自
己負責在何時用free或delete釋放記憶體。動態記憶體的生存期由我們決定,使用非常靈活,但問題也最多。
2.記憶體使用量錯誤
發生記憶體錯誤是件非常麻煩的事情。編譯器不能自動探索這些錯誤,通常是在程式運行時才能捕捉到。
而這些錯誤大多沒有明顯的癥狀,時隱時現,增加了改錯的難度。有時使用者怒氣沖沖地把你找來,程式卻沒有
發生任何問題,你一走,錯誤又發作了。 常見的記憶體錯誤及其對策如下:
* 記憶體配置未成功,卻使用了它。
編程新手常犯這種錯誤,因為他們沒有意識到記憶體配置會不成功。常用解決辦法是,在使用記憶體之前檢查
指標是否為NULL。如果是用malloc或new來申請記憶體,應該用if(p==NULL) 或if(p!=NULL)進行防錯處理。
* 記憶體配置雖然成功,但是尚未初始化就引用它。
犯這種錯誤主要有兩個起因:一是沒有初始化的觀念;二是誤以為記憶體的預設初值全為零,導致引用初值
錯誤(例如數組)。 記憶體的預設初值究竟是什麼並沒有統一的標準,儘管有些時候為零值,我們寧可信其無不
可信其有。所以無論用何種方式建立數組,都別忘了賦初值,即便是賦零值也不可省略,不要嫌麻煩。
* 記憶體配置成功並且已經初始化,但操作越過了記憶體的邊界。
例如在使用數組時經常發生下標“多1”或者“少1”的操作。特別是在for迴圈語句中,迴圈次數很容易搞
錯,導致數組操作越界。
* 忘記了釋放記憶體,造成記憶體泄露。
含有這種錯誤的函數每被調用一次就丟失一塊記憶體。剛開始時系統的記憶體充足,你看不到錯誤。終有一次
程式突然死掉,系統出現提示:記憶體耗盡。
動態記憶體的申請與釋放必須配對,程式中malloc與free的使用次數一定要相同,否則肯定有錯誤
(new/delete同理)。
* 釋放了記憶體卻繼續使用它。
有三種情況:
(1)程式中的對象調用關係過於複雜,實在難以搞清楚某個對象究竟是否已經釋放了記憶體,此時應該重新
設計資料結構,從根本上解決對象管理的混亂局面。
(2)函數的return語句寫錯了,注意不要返回指向“棧記憶體”的“指標”或者“引用”,因為該記憶體在函
數體結束時被自動銷毀。
(3)使用free或delete釋放了記憶體後,沒有將指標設定為NULL。導致產生“野指標”。
【規則1】用malloc或new申請記憶體之後,應該立即檢查指標值是否為NULL。防止使用指標值為NULL的記憶體
【規則2】不要忘記為數組和動態記憶體賦初值。防止將未被初始化的記憶體作為右值使用。
【規則3】避免數組或指標的下標越界,特別要當心發生“多1”或者“少1”操作。
【規則4】動態記憶體的申請與釋放必須配對,防止記憶體流失。
【規則5】用free或delete釋放了記憶體之後,立即將指標設定為NULL,防止產生“野指標”。
二. 詳解new,malloc,GlobalAlloc
1. new
new和delete運算子用於動態分配和撤銷記憶體的運算子
new用法:
1> 開闢單變數地址空間
1)new int; //開闢一個存放數組的儲存空間,返回一個指向該儲存空間的地址.int *a = new
int 即為將一個int類型的地址賦值給整型指標a.
2)int *a = new int(5) 作用同上,但是同時將整數賦值為5
2> 開闢數組空間
一維: int *a = new int[100];開闢一個大小為100的整型數組空間
一般用法: new 類型 [初值]
delete用法:
1> int *a = new int;
delete a; //釋放單個int的空間
2>int *a = new int[5];
delete [] a; //釋放int數組空間
要訪問new所開闢的結構體空間,無法直接通過變數名進行,只能通過賦值的指標進行訪問.
用new和delete可以動態開闢,撤銷地址空間.在編程式時,若用完一個變數(一般是暫時儲存的數組),
下次需要再用,但卻又想省去重新初始化的功夫,可以在每次開始使用時開闢一個空間,在用完後撤銷它.
2. malloc
原型:extern void *malloc(unsigned int num_bytes);
用法:#i nclude <malloc.h>或#i nclude <stdlib.h>
功能:分配長度為num_bytes位元組的記憶體塊
說明:如果分配成功則返回指向被分配記憶體的指標,否則返回null 指標NULL。
當記憶體不再使用時,應使用free()函數將記憶體塊釋放。
malloc的文法是:指標名=(資料類型*)malloc(長度),(資料類型*)表示指標.
說明:malloc 向系統申請分配指定size個位元組的記憶體空間。傳回型別是 void* 類型。void* 表示未確定類型
的指標。C,C++規定,void* 類型可以強制轉換為任何其它類型的指標。
malloc()函數的工作機制
malloc函數的實質體現在,它有一個將可用的記憶體塊串連為一個長長的列表的所謂空閑鏈表。調用malloc
函數時,它沿串連表尋找一個大到足以滿足使用者請求所需要的記憶體塊。然後,將該記憶體塊一分為二(一塊的大
小與使用者請求的大小相等,另一塊的大小就是剩下的位元組)。接下來,將分配給使用者的那塊記憶體傳給使用者,並
將剩下的那塊(如果有的話)返回到串連表上。調用free函數時,它將使用者釋放的記憶體塊串連到空閑鏈上。到
最後,空閑鏈會被切成很多的小記憶體片段,如果這時使用者申請一個大的記憶體片段,那麼空閑鏈上可能沒有可以
滿足使用者要求的片段了。於是,malloc函數請求延時,並開始在空閑鏈上翻箱倒櫃地檢查各記憶體片段,對它們
進行整理,將相鄰的小空閑塊合并成較大的記憶體塊。
和new的不同
從函式宣告上可以看出。malloc 和 new 至少有兩個不同: new 返回指定類型的指標,並且可以自動計算所需
要大小。比如:
int *p;
p = new int; //傳回型別為int* 類型(整數型指標),分配大小為 sizeof(int);
或:
int* parr;
parr = new int [100]; //傳回型別為 int* 類型(整數型指標),分配大小為 sizeof(int) * 100;
而 malloc 則必須由我們計算要位元組數,並且在返回後強行轉換為實際類型的指標。
int* p;
p = (int *) malloc (sizeof(int));
第一、malloc 函數返回的是 void * 類型,如果你寫成:p = malloc (sizeof(int)); 則程式無法通過編譯,
報錯:“不能將 void* 賦值給 int * 類型變數”。所以必須通過 (int *) 來將強制轉換。
第二、函數的實參為 sizeof(int) ,用於指明一個整型資料需要的大小。如果你寫成:
int* p = (int *) malloc (1);
代碼也能通過編譯,但事實上只分配了1個位元組大小的記憶體空間,當你往裡頭存入一個整數,就會有3個位元組無
家可歸,而直接“住進鄰居家”!造成的結果是後面的記憶體中原有資料內容全部被清空。
3. GlobalAlloc
VC中關於GlobalAlloc,GlobalLock,GlobalUnLock
調用GlobalAlloc函數分配一塊記憶體,該函數會返回分配的記憶體控制代碼。
調用GlobalLock函數鎖定記憶體塊,該函數接受一個記憶體控制代碼作為參數,然後返回一個指向被鎖定的記憶體塊的指
針。 您可以用該指標來讀寫記憶體。
調用GlobalUnlock函數來解鎖先前被鎖定的記憶體,該函數使得指向記憶體塊的指標無效。
調用GlobalFree函數來釋放記憶體塊。您必須傳給該函數一個記憶體控制代碼。
GlobalAlloc
說明
分配一個全域記憶體塊
傳回值
Long,返回全域記憶體控制代碼。零表示失敗。會設定GetLastError
參數表
參數 類型及說明
wFlags Long,對分配的記憶體類型進行定義的常數標誌,如下所示:
GMEM_FIXED 分配一個固定記憶體塊
GMEM_MOVEABLE 分配一個可移動記憶體塊
GMEM_DISCARDABLE 分配一個可丟棄記憶體塊
GMEM_NOCOMPACT 堆在這個函數調用期間不進行累積
GMEM_NODISCARD 函數調用期間不丟棄任何記憶體塊
GMEM_ZEROINIT 新分配的記憶體塊全部初始化成零
dwBytes Long,要分配的字元數
GlobalLock
函數功能描述:鎖定一個全域的記憶體對象,返回指向該對象的第一個位元組的指標
函數原型:
LPVOID GlobalLock( HGLOBAL hMem )
參數:
hMem:全域記憶體對象的控制代碼。這個控制代碼是通過GlobalAlloc或GlobalReAlloc來得到的
傳回值:
調用成功,返回指向該對象的第一個位元組的指標
調用失敗,返回NULL,可以用GetLastError來獲得出錯資訊
注意:
調用過GlobalLock鎖定一塊記憶體區後,一定要調用GlobalUnlock來解鎖
GlobalUnlock
函數功能描述:解除被鎖定的全域記憶體對象
函數原型:BOOL GlobalUnlock( HGLOBAL hMem );
參數:hMem:全域記憶體對象的控制代碼
傳回值:
非零值,指定的記憶體對象仍處於被鎖定狀態
0,函數執行出錯,可以用GetLastError來獲得出錯資訊,如果返回NO_ERROR,則表示記憶體對象已經解鎖了
注意: 這個函數實際上是將記憶體對象的鎖定計數器減一,如果計數器不為0,則表示執行過多個GlobalLock
函數來對這個記憶體對象加鎖,需要對應數目的GlobalUnlock函數來解鎖。如果通過GetLastError函數返回錯誤
碼為ERROR_NOT_LOCKED,則表示未加鎖或已經解鎖。
樣本:
// Malloc memory
hMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, nSize);
// Lock memory
pMem = (BYTE *) GlobalLock(hMem);
..................
// Unlock memory
GlobalUnlock(hMem);
GlobalFree(hMem);
三 總結
靈活自由是C/C++語言的一大特色,而這也為C/C++程式員出了一個難題。當程式越來越複雜時,記憶體的管理也
會變得越加複雜,稍有不慎就會出現記憶體問 題。記憶體流失是最常見的記憶體問題之一。記憶體流失如果不是很嚴重
,在短時間內對程式不會有太大的影響,這也使得記憶體流失問題有很強的隱蔽性,不容易被發現。 然而不管內
存泄漏多麼輕微,當程式長時間運行時,其破壞力是驚人的,從效能下降到記憶體耗盡,甚至會影響到其他程式
的正常運行。另外記憶體問題的一個共同特點 是,記憶體問題本身並不會有很明顯的現象,當有異常現象出現時已
時過境遷,其現場已非出現問題時的現場了,這給調試記憶體問題帶來了很大的難度。
下載Windows Debug 工具, http://www.microsoft.com/whdc/devtools/debugging/default.mspx
安裝後,使用其中的gflags.exe工具開啟PageHeap,
gflags -p /enable MainD.exe /full
重新使用VS用調試方式運行,很快就找到了出錯位置,因為在某個靜態函數中筆誤導致
在編寫穩定的伺服器程式時,這個工具尤為有用。
參考文獻及網頁地址:
1. http://www.bccn.net/Article/kfyy/cjj/jszl/200607/4172.html
2. http://www.7880.com/Info/Article-8282a500.html
3. http://www.cnblogs.com/jjzhou1988/archive/2008/11/30/1344314.html
4. http://blog.chinaunix.net/u3/101356/showart_2031203.html
5. http://www.cnblogs.com/howareyou586/archive/2008/11/06/1328353.html