1. 程式人生 > >malloc free new delete

malloc free new delete

malloc/free

malloc工作機制

         malloc函式的實質體現在,它有一個將可 用的記憶體塊連線為一個長長的列表的所謂空閒連結串列。呼叫 malloc 函式時,它沿連線表尋找一個大到足以滿足使用者請求所需要的記憶體塊。然後,將該記憶體塊一分 為二(一塊的大小與使用者請求的大小相等,另一塊的大小就是剩下的位元組)。接下來,將分配給使用者的那塊記憶體傳給使用者,並將剩下的那塊(如果有的話)返回到連 接表上。呼叫 free 函式時,它將使用者釋放的記憶體塊連線到空閒鏈上。到最後,空閒鏈會被切成很多的小記憶體片段,如果這時使用者申請一個大的記憶體片段,那麼空 閒鏈上可能沒有可以滿足使用者要求的片段了。於是, malloc 函式請求延時,並開始在空閒鏈上翻箱倒櫃地檢查各記憶體片段,對它們進行整理,將相鄰的小空閒 塊合併成較大的記憶體塊。


       分配記憶體,在size的基礎上加sizeof(struct mem_control_block)  個位元組。我們主要使用連線的指標遍歷記憶體來尋找開放的記憶體塊。釋放記憶體,將引數指標回退 sizeof(struct mem_control_block)  個位元組,並將其標記為可用的。

C語言是跨平臺的,最終的記憶體處理都是交給系統API完成。系統會記錄每一塊分配記憶體的地址,大小,釋放情況等等。所以malloc只需傳記憶體大小引數,free只需要傳一個地址的引數就可以了。而且同一個地址不能釋放兩次。

debug

debug版本下malloc需要分配的記憶體會比實際的size

36byte。最終分配的記憶體塊如下:

_CrtMemBlockHeader是一個雙向連結串列結構,其定義如下:


typedef struct _CrtMemBlockHeader 

     struct _CrtMemBlockHeader *pBlockHeaderNext;   //下次分配的記憶體塊

     struct _CrtMemBlockHeader *pBlockHeaderPrev;   //上次分配的記憶體塊

     char *szFileName;              //分配記憶體程式碼的檔名

     int nLine;                  //

分配記憶體程式碼的行號

     size_tnDataSize;               //請求的大小,如例項中的

     int nBlockUse;                 //請求的記憶體型別,如例項中的user型別

     long lRequest;                 //請求id,每次請求都會被記錄

     unsigned char gap[nNoMansLandSize];     //4位元組校驗位

} _CrtMemBlockHeader; 

        使用者請求記憶體前後分別有4位元組的校驗位,分配記憶體後都會被初始化為0xFD。如果這8個位元組被改寫,free時就會觸發斷言失敗。而請求的32位元組會被初始化為0xCC(和棧的初始化一樣)。

       系統通過記錄這些資訊就能顯示的給出錯誤。比如越界訪問請求的記憶體在debug下會斷言失敗,release下面則不會,從而這會給程式埋下巨大的隱患。很多在release下偶發的錯誤就是這樣產生的。_CrtMemBlockHeader總共32位元組,加上使用者請求的32位元組及最後4位元組校驗位是68位元組。最終呼叫系統的API請求記憶體。比如Windows下面是HeapAlloc

如果記憶體分配失敗,malloc不像new那樣可以呼叫new_handler來處理,它直接返回NULL

         free則是對_CrtMemBlockHeader的資訊做清理操作,檢查校驗位等等。最終呼叫系統API釋放記憶體。比如Windows下面是HeapFree

release

實際分配的記憶體等於請求的記憶體大小。malloc和free只是在系統API之上做了些判斷操作。

記憶體碎片

記憶體分配的原理

從作業系統角度來看,程序分配記憶體有兩種方式,分別由兩個系統呼叫完成:brk和mmap(不考慮共享記憶體)。

1、brk是將資料段(.data)的最高地址指標_edata往高地址推;

2、mmap是在程序的虛擬地址空間中(堆和棧中間,稱為檔案對映區域的地方)找一塊空閒的虛擬記憶體

     這兩種方式分配的都是虛擬記憶體,沒有分配實體記憶體在第一次訪問已分配的虛擬地址空間的時候,發生缺頁中斷,作業系統負責分配實體記憶體,然後建立虛擬記憶體和實體記憶體之間的對映關係。
在標準C庫中,提供了malloc/free函式分配釋放記憶體,這兩個函式底層是由brk,mmap,munmap這些系統呼叫實現的。


下面以一個例子來說明記憶體分配的原理:

情況一:malloc小於128k的記憶體,使用brk分配記憶體,將_edata往高地址推(只分配虛擬空間,不對應實體記憶體(因此沒有初始化),第一次讀/寫資料時,引起核心缺頁中斷,核心才分配對應的實體記憶體,然後虛擬地址空間建立對映關係),如下圖:


1.程序啟動的時候,其(虛擬)記憶體空間的初始佈局如圖1所示。

     其中,mmap記憶體對映檔案是在堆和棧的中間(例如libc-2.2.93.so,其它資料檔案等),為了簡單起見,省略了記憶體對映檔案。_edata指標(glibc裡面定義)指向資料段的最高地址。 
2.
程序呼叫A=malloc(30K)以後,記憶體空間如圖2:

      malloc函式會呼叫brk系統呼叫,將_edata指標往高地址推30K,就完成虛擬記憶體分配。

      你可能會問:只要把_edata+30K就完成記憶體分配了?

      事實是這樣的,_edata+30K只是完成虛擬地址的分配,A這塊記憶體現在還是沒有物理頁與之對應的,等到程序第一次讀寫A這塊記憶體的時候,發生缺頁中斷,這個時候,核心才分配A這塊記憶體對應的物理頁。也就是說,如果用malloc分配了A這塊內容,然後從來不訪問它,那麼,A對應的物理頁是不會被分配的。 
3.
程序呼叫B=malloc(40K)以後,記憶體空間如圖3。

情況二: malloc大於128k的記憶體,使用mmap分配記憶體,在堆和棧之間找一塊空閒記憶體分配(對應獨立記憶體,而且初始化為0),如下圖:


4、程序呼叫C=malloc(200K)以後,記憶體空間如圖4:

      預設情況下,malloc函式分配記憶體,如果請求記憶體大於128K(可由M_MMAP_THRESHOLD選項調節),那就不是去推_edata指標了,而是利用mmap系統呼叫,從堆和棧的中間分配一塊虛擬記憶體。

      這樣子做主要是因為brk分配的記憶體需要等到高地址記憶體釋放以後才能釋放(例如,在B釋放之前,A是不可能釋放的,這就是記憶體碎片產生的原因,什麼時候緊縮看下面),而mmap分配的記憶體可以單獨釋放。

當然,還有其它的好處,也有壞處,再具體下去,有興趣的同學可以去看glibc裡面malloc的程式碼了。 
5、程序呼叫D=malloc(100K)以後,記憶體空間如圖5;
6、程序呼叫free(C)以後,C對應的虛擬記憶體和實體記憶體一起釋放。


7、程序呼叫free(B)以後,如圖7所示:

        B對應的虛擬記憶體和實體記憶體都沒有釋放,因為只有一個_edata指標,如果往回推,那麼D這塊記憶體怎麼辦呢

當然,B這塊記憶體,是可以重用的,如果這個時候再來一個40K的請求,那麼malloc很可能就把B這塊記憶體返回回去了。 
8、程序呼叫free(D)以後,如圖8所示:

        B和D連線起來,變成一塊140K的空閒記憶體。

9、預設情況下:

       當最高地址空間的空閒記憶體超過128K(可由M_TRIM_THRESHOLD選項調節)時,執行記憶體緊縮操作(trim)。在上一個步驟free的時候,發現最高地址空閒記憶體超過128K,於是記憶體緊縮

malloc/free和new/delete

         malloc  和  new  至少有兩個不同 : new  返回指定型別的指標,並且可以自動計算所需要大小。malloc  只管分配記憶體,並不能對所得的記憶體進行初始化,所以得到的一片新記憶體中,其值將是隨機的。malloc 與 free  是 C++/C  語言的標準庫函式, new/delete  是 C++ 的運算子。它們都可 用於申請動態記憶體和釋放記憶體。 對於非內部資料型別的物件而言,光用maloc/free  無法滿足動態物件的要求。物件 在建立的同時要自動執行建構函式, 物件在消亡之前要自動執行解構函式。由於 malloc/free 是庫函式而不是運算子,不在編譯器控制權限之內,不能夠把執行建構函式和解構函式的任務強加於malloc/free 。 因此C++ 語言需要一個能完成動態記憶體分配和初始化工作的運算子 new ,以及一個 能完成清理與釋放記憶體工作的運算子delete 。注意 new/delete  不是庫函式。如果沒有足夠記憶體空間而導致malloc申請失敗,返回NULL。而new則丟擲std::bad_alloc標準異常,不返回NULL。

malloc和new的區別

1)new 返回指定型別的指標,並且可以自動計算所需要大小。而 malloc 則必須要由我們計算位元組數,並且在返回後強行轉換為實際型別的指標。   
2)malloc 只管分配記憶體,並不能對所得的記憶體進行初始化,所以得到的一片新記憶體中,其值將是隨機的。除了分配及最後釋放的方法不一樣以外,通過malloc或new得到指標,在其它操作上保持一致。

有了malloc/free為什麼還要new/delete?

1) malloc與free是C++/C語言的標準庫函式,new/delete是C++的運算子。它們都可用於申請動態記憶體和釋放記憶體。
2) 對於非內部資料型別的物件而言,光用maloc/free無法滿足動態物件的要求。物件在建立的同時要自動執行建構函式,物件在消亡之前要自動執行解構函式。由於malloc/free是庫函式而不是運算子,不在編譯器控制權限之內,不能夠把執行建構函式和解構函式的任務強加於malloc/free。
       因此C++語言需要一個能完成動態記憶體分配和初始化工作的運算子new,以及一個能完成清理與釋放記憶體工作的運算子delete。注意new/delete不是庫函式。
       我們不要企圖用malloc/free來完成動態物件的記憶體管理,應該用new/delete。由於內部資料型別的“物件”沒有構造與析構的過程,對它們而言malloc/free和new/delete是等價的。
既然new/delete的功能完全覆蓋了malloc/free,為什麼C++不把malloc/free淘汰出局呢?
          這是因為C++程式經常要呼叫C函式,而C程式只能用malloc/free管理動態記憶體。