1.問題的引入:
為什么要使用malloc,主要是因為在代碼中,為了節約內存,很多數據都是動態生成的,所以會用malloc,對應於C++中的new,底層還是調用malloc。
2.碎片的問題:
會有內部碎片與外部碎片的問題,內部碎片難以消除(因為字對齊之類的問題),而外部碎片是可以消除的(如果不消除的話,外部的內存塊越來越小,雖然數量多了,但是利用率會急劇下降!)
3.需要解決的問題:
4.隱式的空閑鏈表:
一個簡單的堆塊,這里只有頭部,下面為了優化,還會使用尾部。
隱式的空閑鏈表:
這樣就把用鏈表的方法把堆空間給聯系起來了。特點明顯,實現簡單,但分配時查詢空堆塊是線性時間的。
5.放置以分配的塊:
1)、首次適配:從鏈表開始尋找適合的空堆塊,直到找到為止
2)、下一次適配:從上一次的分配點開始找,直到找到適合的空堆塊為止
3)、最佳適配:查詢整個鏈表,找到最合適(浪費最小)的空堆塊
6.分割空閑塊:
當前的分配的空堆塊比所需要的空間大很多的時候,如果不分割,就會照成內部碎片過大,利用率下降。
當需要3個字節的空間時,首次適配找到了滿足條件的第二個堆塊,但堆塊過大,所以分割成兩個16字節的堆塊。
7.獲取額外的堆存儲器:
當鏈表中不能滿足申請要求的堆塊空間的時候,1)通過合並相鄰的堆塊空間,形成單個盡量大的堆塊空間 2)實在沒有其他辦法了,分配器通過sbrk函數向內核申請格外的堆空間,分配器將堆空間插入到鏈表中,然后提供給申請空間的塊。
8.合並
合並兩個塊,由於當前塊不知道上一個塊的情況,它要通過遍歷整個鏈表才能知道上一個堆塊的空間是否為空,這樣將花費線性的時間來合並與當前相鄰的空閑堆塊。有個類似雙向鏈表的方法(不過不是真正的雙向鏈表,只是可以加一個信息量,直接就可以知道上一個堆塊的情況)。
共有四種情況:
從而解決了堆塊的合並問題。
總結:
malloc函數的實質體現在,它有一個將可用的內存塊連接為一個長長的列表的所謂空閑鏈表。調用malloc函數時,它沿鏈表尋找一個大到足以滿足用戶請求所需要的內存塊。然后,將該內存塊一分為二(一塊的大小與用戶請求的大小相等,另一塊的大小就是剩下的字節)。接下來,將分配給用戶的那塊內存傳給用戶,並將剩下的那塊(如果有的話)返回到連接表上。調用free函數時,它將用戶釋放的內存塊連接到空閑鏈上。到最后,空閑鏈會被切成很多的小內存片段,如果這時用戶申請一個大的內存片段,那么空閑鏈上可能沒有可以滿足用戶要求的片段了。於是,malloc函數請求延時,並開始在空閑鏈上翻箱倒櫃地檢查各內存片段,對它們進行整理,將相鄰的小空閑塊合並成較大的內存塊。如果無法獲得符合要求的內存塊,malloc函數會返回NULL指針,因此在調用malloc動態申請內存塊時,一定要進行返回值的判斷。