記憶體分配詳解 malloc, new, HeapAlloc, VirtualAlloc,GlobalAlloc

黨偉_90發表於2018-05-03

很多地方都會使用記憶體,記憶體使用過程中操作不當就容易崩潰,無法執行程式,上網Google學習一下,瞭解整理下他們之間的區別以及使用 ,獲益匪淺

0x01 各自的定義和理解

   (1)先看GlobalAlloc()

    GlobalAlloc()主要用於Win32應用程式實現從全域性堆中分配出記憶體供2017-03-05程式使用,是16位WINDOWS程式使用的API,對應於系統的全域性棧,返回一個記憶體控制程式碼,在實際需要使用時,用GlobalLock()來實際得到記憶體 區。但32位WINDOWS系統中全域性棧和區域性堆的區別已經不存在了,因此不推薦在Win32中使用該函式,應使用新的記憶體分配函式HeapAlloc()以得到更好的支援,GlobalAlloc()還可以用,主要是為了 相容。

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

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

  (2)HeapAlloc()

    HeapALloc是從堆上分配一塊記憶體,且分配的記憶體是不可移動的(即如果沒有連續的空間能滿足分配的大小,程式不能將其他零散的 空間利用起來,從而導致分配失敗),該分配方法是從一指定地址開始分配,而不像GloabalAlloc是從全域性堆上分配,這個有可能是全域性,也有可能是 區域性

  (3)malloc()

  是C執行庫中的動態記憶體分配函式,主要用於ANSI C程式中,是標準庫函式。WINDOWS程式基本不再使用這種方法進行記憶體操作,因為它比WINDOWS記憶體分配函式少了一些特性,如整理記憶體

  (4)new

標準C++一般使用new語句分配動態的記憶體空間,需要申請陣列時,可以直接使用new int[3]這樣的方式,釋放該方法申請的記憶體空間使用對應的delete語句,需要釋放的記憶體空間為一個陣列,則使用delete [] ary;這樣的方式。

要訪問new所開闢的結構體空間,無法直接通過變數名進行,只能通過賦值的指標進行訪問.

new在內部呼叫malloc來分配記憶體,delete在內部呼叫free來釋放記憶體。

  (5)

(1) VirtualAlloc  下面是網友的解釋 但我個人的理解這個才是記憶體申請的鼻祖,所有的記憶體的申請都感覺預設呼叫了它

    PVOID VirtualAlloc(PVOID pvAddress, SIZE_T dwSize, DWORD fdwAllocationType, DWORD fdwProtect)

VirtualAlloc是Windows提供的API,通常用來分配大塊的記憶體。例如如果想在程式A和程式B之間通過共享記憶體的方式實現通訊,可以使用該函式(這也是較常用的情況)。不要用該函式實現通常情況的記憶體分配。該函式的一個重要特性是可以預定指定地址和大小的虛擬記憶體空間。例如,希望在程式的地址空間中第50MB的地方分配記憶體,那麼將引數 50*1024*`1024 = 52428800 傳遞給pvAddress,將需要的記憶體大小傳遞給dwSize。如果系統有足夠大的閒置區域能滿足請求,則系統會將該塊區域預訂下來並返回預訂記憶體的基地址,否則返回NULL。

使用VirtualAlloc分配的記憶體需要使用VirtualFree來釋放。

  0x02 區別與聯絡

它們之間的區別主要有以下幾點:

1、GlobalAlloc()函式在程式的堆中分配一定的記憶體,是Win16的函式,對應於系統的全域性棧,而在Win32中全域性棧和區域性堆的區別已經不存在了,因此不推薦在Win32中使用該函式。

2、malloc()是標準庫函式,而new則是運算子,它們都可以用於申請動態記憶體。

3、new()實際上呼叫的是malloc()函式。

4、new運算子除了分配記憶體,還可以呼叫建構函式,但是malloc()函式只負責分配記憶體。

5、對於非內部資料型別的物件而言,只使用malloc()函式將無法滿足動態物件的要求,因為malloc()函式不能完成執行建構函式的任務。

6、malloc(); 和 HeapAlloc(); 都是從堆中分配相應的記憶體,不同的是一個是c run time的函式,一個是windows系統的函式, 對於windows程式來說,使用HeapAlloc();會比malloc();效率稍稍高一些。

  0x03關於記憶體的初始化和使用

    

    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,防止產生“野指標”。

相關文章