C++學習011-經常使用內存分配及釋放函數

 

C++用有多種方法來分配及釋放內存,下面是一些常用的內存分配及釋放函數html

 

如今我仍是一個技術小白,通常用到也指示 new+delete 和 malloc和free程序員

 

其餘的也是在學習中看到,下面的文字來之「笨小鳥」的文章編程

鏈接地址:http://blog.csdn.net/wxq1987525/article/details/7462446windows

文章詳細內容數組

 

一。關於內存安全

 一、內存分配方式服務器

  內存分配方式有三種:數據結構

  (1)從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在app

。例如全局變量,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。 
  當內存再也不使用時,應使用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));
第1、malloc 函數返回的是 void * 類型,若是你寫成:p = malloc (sizeof(int)); 則程序沒法經過編譯,

報錯:「不能將 void* 賦值給 int * 類型變量」。因此必須經過 (int *) 來將強制轉換。
第2、函數的實參爲 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的用法及疑問

調用GlobalAlloc函數分配一塊內存,該函數會返回分配的內存句柄。 
調用GlobalLock函數鎖定內存塊,該函數接受一個內存句柄做爲參數,而後返回一個指向被鎖定的內存塊的指針。 您能夠用該指針來讀寫內存。 
調用GlobalUnlock函數來解鎖先前被鎖定的內存,該函數使得指向內存塊的指針無效。 
調用GlobalFree函數來釋放內存塊。您必須傳給該函數一個內存句柄。

GlobalAlloc
說明 
分配一個全局內存塊 
返回值 
Long,返回全局內存句柄。零表示失敗。會設置GetLastError 
參數表 
參數 類型及說明 
wFlags Long,對分配的內存類型進行定義的常數標誌,以下所示: 
             GMEM_FIXED 分配一個固定內存塊 
             GMEM_MOVEABLE 分配一個可移動內存塊 
             GMEM_DISCARDABLE 分配一個可丟棄內存塊 
             GMEM_NOCOMPACT 堆在這個函數調用期間不進行累積 
             GMEM_NODISCARD 函數調用期間不丟棄任何內存塊 
             GMEM_ZEROINIT 新分配的內存塊所有初始化成零 
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
函數功能描述:鎖定一個全局的內存對象,返回指向該對象的第一個字節的指針
函數原型:
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);

 

 

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(
UINT uFlags,
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指針。

 

 

 

相關文章
相關標籤/搜索