malloc()與free()
l 函數原型
malloc函數的函數原型為:void* malloc(unsigned int size),它根據參數指定的尺寸來分配內存塊,並且返回一個void型指針,指向新分配的內存塊的初始位置。如果內存分配失敗(內存不足),則函數返回NULL。
l 關於返回值
malloc的返回值為void*。我們在使用的時候,習慣對返回值進行強制類型轉換:
char * p = NULL;
p = (char *)malloc(sizeof(char));
ANSI C以前的C,因為沒有void*這種類型,malloc函數的返回值被簡單地定義為char*,char*是不能被賦予指向其他類型變量的指針的。所以在使用malloc函數時通常需要對其返回值進行強制類型轉換。
在ANSI C中,malloc函數的返回值為void*。void*類型是可以直接賦值給其他任何類型的指針。所以,上面的強制類型轉換操作現在已經不需要了。
然而在c++中,任何類型的指針都可以賦給void*,而void*卻不可以賦給其他類型的指針,所以在c++中使用malloc函數的時候,強制類型轉換是必須的。另一方面,在c++中應該使用new來分配內存。
l malloc在堆上分配內存
malloc函數分配的內存是在堆(heap)上的。操作系統有一個記錄空閑內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,然后將該結點從空閑結點鏈表中刪除,並將該結點的空間分配給程序,另外,對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣代碼中的delete或free語句才能正確的釋放本內存空間。我們常說的內存泄露,最常見的就是堆泄露(還有資源泄露),它是指程序在運行中出現泄露,如果程序被關閉掉的話,操作系統會幫助釋放泄漏的內存。
l malloc的使用
malloc函數使用起來倒是挺簡單的,主要的使用范例有兩種:一是動態分配結構體,通常用於被稱為“鏈表”的數據結構中;二是分配可變長度的數組。對這兩種用法就不多說了,主要是來看使用過程中的注意點:
- 調用malloc函數后,應該對函數返回值進行檢查。前面說過,內存分配一旦失敗,malloc()會返回NULL。
- char * p = NULL;
- p = (char *)malloc(sizeof(char));
- if(!p)
- exit(1);
- 在程序結束時,應該調用free函數對malloc函數分配的內存進行釋放。
實際上,c語言標准沒有規定要這么做,而且普通的PC上的操作系統,在進程結束時,肯定會釋放曾經分配給當前進程的內存空間,也就是說,在程序結束之前,沒有必要調用free()。但是,對於一串連續的程序處理事件,如果先前程序分配的內存沒有及時釋放掉,那后面的工作就遭殃了。所以”malloc與free配套出現”還是相當合理的。
l malloc()與free( )
從操作系統一次性地取得比較大的內存,當程序調用malloc()時,malloc()便將內存”零售”給應用程序,這是malloc()的大體實現。而當這塊一次性取出來的內存不夠用的時候,就請求操作系統對空間進行擴容。多次調用malloc()(導致內存不夠用了)會調用一次brk(),內存區域向地址較大的一方伸長。malloc()分配內存,會用到brk(用於小內存申請<=128kb,在堆上)或mmap2(用於大內存申請,一般是堆和棧中間)系統調用 。
K&R中記錄了malloc()最簡單的一種實現方式:通過鏈表來實現。malloc管理的空間不一定是連續的,空閑存儲空間以空閑塊鏈表的方式組織。在這種方式下,每個塊之前都加上了一個管理區域,包含一個長度、一個指向下一塊的指針以及一個指向自身存儲空間的指針。這些快按照儲存地址的升序組織。最后一塊(最高地址)指向第一塊。這里使用K&R中的圖加以說明:

當有申請要求時,malloc將掃描空閑塊鏈表,直到找到一塊足夠大的空閑塊為止,如果找不到,則向操作系統申請一個大塊並加入到空閑鏈表中。然而在這種內存管理方式的運行環境中,一旦數組越界檢查發生錯誤,越過了malloc()分配的內存區域寫入了數據,將會破壞下一個塊的管理區域,容易造成程序崩潰。在《UNIX環境高級編程》中有一段話肯定了以上的說法:
“大多數實現所分配的存儲空間比所要求的要稍大一些,額外的空間用來記錄管理信息——分配塊的長度,指向下一個分配塊的指針等等。這就意味着如果寫過一個已分配區的尾端,則會改寫后一塊的管理信息。這種類型的錯誤是災難性的,但是因為這種錯誤不會很快就暴露出來,所以也就很難發現。將指向分配塊的指針向后移動也可能會改寫本塊的管理信息。”
那么,free()在這里做了什么呢?free()將管理區域的標記改為”空塊”,順便也將上下空塊合並成一個塊,這樣也防止了塊的碎片化。這么說來,free()函數在調用后,對應的內存是不會立刻返還給操作系統的(還在空閑鏈表里呆着)。也就是說,調用了free()之后,對應內存的內容不會馬上被破壞,直到該塊內存被重新分配,里面的內容才會被覆蓋重寫。盡管如此,調用free()之后,是不能引用對應的內存區域的。所以倉促地使用free()是不對的,特別是當有兩個指針指向同一塊內存時,指針1把內存釋放了,而指針2還指向那塊內存,然而指針2已經不能進行解引用了。
這么看來,free()函數實際上並沒有做”釋放”的實際操作,它只是改變一些狀態,告知操作系統某塊內存可以去釋放。至於如何告訴,還需要后續了解。
