天天看點

C++學習011-常用記憶體配置設定及釋放函數

C++學習011-常用記憶體配置設定及釋放函數

現在我還是一個技術小白,一般用到也訓示 new+delete 和 malloc和free

其他的也是在學習中看到,下面的文字來之“笨小鳥”的文章

連接配接位址:

http://blog.csdn.net/wxq1987525/article/details/7462446

文章詳細内容

一。關于記憶體

 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 或#i nclude

 功能:配置設定長度為num_bytes位元組的記憶體塊

 說明:如果配置設定成功則傳回指向被配置設定記憶體的指針,否則傳回空指針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用調試方式運作,很快就找到了出錯位置,因為在某個靜态函數中筆誤導緻

在編寫穩定的伺服器程式時,這個工具尤為有用。

關于記憶體方面的内容還有另一個大神(歌行梅村)寫的文章

文章連接配接:

http://blog.csdn.net/chenjie863/article/details/16824501

原文内容:

1.為什麼要用GlobalLock()函數

C/C++ code

  HGLOBAL hImageMemory=GlobalAlloc(GMEM_MOVEABLE, dwFileSize); //給圖檔配置設定全局記憶體  

  void *pImageMemory=GlobalLock(hImageMemory); //鎖定記憶體  

  DWORD dwReadedSize; //儲存實際讀取的檔案大小  

  ReadFile(hFile, pImageMemory, dwFileSize, &dwReadedSize, NULL); //讀取圖檔到全局記憶體當中  

  GlobalUnlock(hImageMemory); //解鎖記憶體  

  CloseHandle(hFile); //關閉檔案句柄  

  IStream *pIStream;//建立一個IStream接口指針,用來儲存圖檔流

解答:

 lobalAlloc申請的記憶體分兩種,一種是GMEM_FIXED,另一種是GMEM_MOVEABLE。

 兩者的差别隻要在于GMEM_MOVEABLE類型的記憶體作業系統是可以移動的,比如堆中有好幾塊小記憶體,

  當再申請一大塊記憶體時,作業系統會移動GMEM_MOVEABLE類型的記憶體來合并出一大塊。

 正因為GMEM_MOVEABLE是可移動的,是以要用句柄辨別,不能用記憶體位址辨別,

  在使用時通過GlobalLock由句柄得到記憶體位址。

  對于GMEM_FIXED類型的,該函數傳回的句柄就是記憶體指針,可以直接當記憶體指針使用。

出處:

http://topic.csdn.net/u/20100802/17/2e66b3ef-285d-43da-b5a2-60f8d0665fbd.html

2. VC中關于GlobalAlloc,GlobalLock,GlobalUnLock的用法及疑問

調用GlobalLock函數鎖定記憶體塊,該函數接受一個記憶體句柄作為參數,然後傳回一個指向被鎖定的記憶體塊的指針。 您可以用該指針來讀寫記憶體。

GlobalAlloc

dwBytes Long,要配置設定的字元數

注解

如指定了 GMEM_FIXED,那麼傳回值就是要使用的實際記憶體位址即指針(GlobalLock 會傳回同樣的值)——是以在使用固定記憶體塊的時候不需要執行一個 GlobalLock/GlobalUnlock 操作

由于 Win32 采用了進階的記憶體管理方案,是以使用可移動的記憶體塊并沒有什麼好處

用這個函數配置設定的記憶體塊允許在8位邊界以内

【附】關于GlobalAlloc的問題

--------------------------------------------------------------------------------

問:在使用 GlobalAlloc 配置設定一個全局記憶體塊時,使用GMEM_FIXED配置設定一個固定記憶體塊與使用GMEM_MOVEABLE分 配一個可移動記憶體塊到底有什麼不同?(請 具 體 點)

其效率上是否也存在差異?

為什麼在有些源碼中,再使用GMEM_MOVEABLE标志配置設定記憶體時,将使用GlobalFree對其傳回的記憶體句柄進行釋放操作的語句注釋掉,或者幹脆就不寫?難道是不需要這麼做嗎?

答:GMEM_MOVEABLE是允許作業系統(或者應用程式)實施對記憶體堆的管理,在必要時,作業系統可以移動記憶體塊擷取更大的塊,或者合并一些空閑的記憶體塊,也稱“垃圾回收”,它可以提高記憶體的使用率。一般情況下,記憶體堆空間是由使用者來管理的,windows作業系統不幹預。如果存在下列情況,即堆中有10個1K的空閑塊,這時如果直接申請一個5K的記憶體空間,會得到不成功的資訊。但如果其它已經被占用的記憶體塊是movable,這時系統就可以移動這些記憶體塊,合并出一個5k的記憶體塊,并成功配置設定給使用者使用。它的空間效率是以運作時的時間效率為代價的。

GlobalLock

調用過GlobalLock鎖定一塊記憶體區後,一定要調用GlobalUnlock來解鎖。

GlobalUnlock

注意:    這個函數實際上是将記憶體對象的鎖定計數器減一,如果計數器不為0,則表示執行過多個GlobalLock函數來對這個記憶體對象加鎖,需要對應數目的GlobalUnlock函數來解鎖。

   如果通過GetLastError函數傳回錯誤碼為ERROR_NOT_LOCKED,則表示未加鎖或已經解鎖。

示例:

1. HeapAlloc:

MSDN上的解釋為:HeapALloc是從堆上配置設定一塊記憶體,且配置設定的記憶體是不可移動的(即如果沒有連續的空間能滿足配置設定的大小,程式不能将其他零散的 空間利用起來,進而導緻配置設定失敗),該配置設定方法是從一指定位址開始配置設定,而不像GloabalAlloc是從全局堆上配置設定,這個有可能是全局,也有可能是 局部。函數原型為:

LPVOID

HeapAlloc(

   HANDLE hHeap,

   DWORD dwFlags,

  SIZE_T dwBytes

   );

hHeap是程序堆記憶體開始位置。

dwFlags是配置設定堆記憶體的标志。包括HEAP_ZERO_MEMORY,即使配置設定的空間清零。

dwBytes是配置設定堆記憶體的大小。

其對應的釋放空間函數為HeapFree。

2. GlobalAlloc

該函數用于從全局堆中配置設定出記憶體供程式使用,函數原型為:

HGLOBAL GlobalAlloc(

UINT uFlags,

SIZE_T dwBytes

);

uFlags參數含義

GHND   GMEM_MOVEABLE和GMEM_ZEROINIT的組合

GMEM_FIXED   配置設定固定記憶體,傳回值是一個指針

GMEM_MOVEABLE   配置設定活動記憶體,在Win32中,記憶體塊不能在實體記憶體中移動,但能在預設的堆中移動。傳回值是記憶體對象的句柄,用函數GlobalLock可将句柄轉化為指針

GMEM_ZEROINIT   将記憶體内容初始化為零

GPTR   GMEM_FIXED和GMEM_ZEROINIT的組合

一般情況下我們在程式設計的時候,給應用程式配置設定的記憶體都是可以移動的或者是可以丢棄的,這樣能使有限的記憶體資源充分利用,是以,在某一個時候我們配置設定的那塊 記憶體的位址是不确定的,因為他是可以移動的,是以得先鎖定那塊記憶體塊,這兒應用程式需要調用API函數GlobalLock函數來鎖定句柄。如下: lpMem=GlobalLock(hMem); 這樣應用程式才能存取這塊記憶體。是以我們在使用GlobalAllock時,通常搭配使用GlobalLock,當然在不使用記憶體時,一定記得使用 GlobalUnlock,否則被鎖定的記憶體塊一直不能被其他變量使用。

GlobalAlloc對應的釋放空間的函數為GlobalFree。

3. LocalAlloc

該函數用于從局部堆中配置設定記憶體供程式使用,函數原型為:

HLOCAL LocalAlloc(

SIZE_T uBytes

參數同GlobalAlloc。

在16位Windows中是有差別的,因為在16位windows用一個全局堆和局部堆來管理記憶體,每一個應用程式或dll裝入記憶體時,代碼段被裝入全局 堆,而系統又為每個執行個體從全局堆中配置設定了一個64kb的資料段作為該執行個體的局部堆,用來存放應用程式的堆棧和所有全局或靜态變量。而 LocalAlloc/GlobalAlloc就是分别用于在局部堆或全局堆中配置設定記憶體。

由于每個程序的局部堆很小,是以在局部堆中配置設定記憶體會受到空間的限制。但這個堆是每個程序私有的,相對而言配置設定資料較安全,資料通路出錯不至于影響到整個系統。

而在全局堆中配置設定的記憶體是為各個程序共享的,每個程序隻要擁有這個記憶體塊的句柄都可以通路這塊記憶體,但是每個全局記憶體空間需要額外的記憶體開銷,造成配置設定浪費。而且一旦發生嚴重錯誤,可能會影響到整個系統的穩定。

不過在Win32中,每個程序都隻擁有一個省缺的私有堆,它隻能被目前程序通路。應用程式也不可能直接通路系統記憶體。是以在Win32中全局堆和局部堆都 指向程序的省缺堆。用LocalAlloc/GlobalAlloc配置設定記憶體沒有任何差別。甚至LocalAlloc配置設定的記憶體可以被 GlobalFree釋放掉。是以在Win32下程式設計,無需注意Local和Global的差別,一般的記憶體配置設定都等效于 HeapAlloc(GetProcessHeap(),...)。

LocalAlloc對應的釋放函數為LockFree。

4. VirtualAlloc

該函數的功能是在調用程序的虛位址空間,預定或者送出一部分頁,如果用于記憶體配置設定的話,并且配置設定類型未指定MEM_RESET,則系統将自動設定為0;其函數原型:

LPVOID VirtualAlloc(

LPVOID lpAddress, // region to reserve or commit

SIZE_T dwSize, // size of region

DWORD flAllocationType, // type of allocation

DWORD flProtect // type of access protection

VirtualAlloc可以通過并行多次調用送出一個區域的部分或全部來保留一個大的記憶體區域。多重調用送出同一塊區域不會引起失敗。這使得一個應用程 序保留記憶體後可以随意送出将被寫的頁。當這種方式不在有效的時候,它會釋放應用程式通過檢測被保留頁的狀态看它是否在送出調用之前已經被送出。

VirtualAlloc對應的釋放函數為VirtualFree。

5.Malloc

malloc與free是C++/C語言的标準庫函數,可用于申請動态記憶體和釋放記憶體。對于非内部資料類型的對象而言,光用 malloc/free無法滿足動态對象的要求。對象在建立的同時要自動執行構造函數,對象在消亡之前要自動執行析構函數。由于malloc/free是 庫函數而不是運算符,不在編譯器控制權限之内,不能夠把執行構造函數和析構函數的任務強加于malloc/free。

6.New

new/delete是C++的運算符。可用于申請動态記憶體和釋放記憶體。C++語言需要一個能完成動态記憶體配置設定和初始化工作的運算符new, 以一個能完成清理與釋放記憶體工作的運算符delete。注意new/delete不是庫函數。C++程式經常要調用C函數,而C程式隻能用malloc /free管理動态記憶體。new 是個操作符,和什麼"+","-","="...有一樣的地位.

       malloc是個配置設定記憶體的函數,供你調用的.

       new是保留字,不需要頭檔案支援.

       malloc需要頭檔案庫函數支援.new 建立的是一個對象,

       malloc配置設定的是一塊記憶體.

       new建立的對象你可以把它當成一個普通的對象,用成員函數通路,不要直接通路它的位址空間

       malloc配置設定的是一塊記憶體區域,就用指針通路好了,而且還可以在裡面移動指針.

記憶體洩漏對于malloc或者new都可以檢查出來的,差別在于new可以指明是那個檔案的那一行,而malloc沒有這些資訊。new可以認為是malloc加構造函數的執行。new出來的指針是直接帶類型資訊的。而malloc傳回的都是void指針。