1. 程式人生 > >資料結構之動態記憶體管理機制

資料結構之動態記憶體管理機制

通過前面的學習,介紹很多具體的資料結構的儲存以及遍歷的方式,過程中只是很表面地介紹了資料的儲存,而沒有涉及到更底層的有關的儲存空間的分配與回收,從本節開始將做更深入地介紹。

在使用早期的計算機上編寫程式時,有關資料儲存在什麼位置等這樣的問題都是需要程式設計師自己來給資料分配記憶體。而現在的高階語言,大大的減少了程式設計師的工作,不需要直接和儲存空間打交道,程式在編譯時由編譯程式去合理地分配空間。

本章重點解決的問題是
  • 對於使用者向系統提出的申請空間的請求,系統如何分配記憶體?
  • 當用戶不在使用之前申請的記憶體空間後,系統又如何回收?
這裡的使用者,不是普通意義上的使用者,可能是一個普通的變數,一個應用程式,一個命令等等。只要是向系統發出記憶體申請的,都可以稱之為使用者

佔用塊和空閒塊

對於計算機中的記憶體來說,稱已經分配給使用者的的記憶體區統稱為
“佔用塊”還未分配出去的記憶體區統稱為“空閒塊”或者“可利用空間塊”

系統的記憶體管理

對於初始狀態下的記憶體來說,整個空間都是一個空閒塊(在編譯程式中稱為“堆”)。但是隨著不同的使用者不斷地提出儲存請求,系統依次分配。

整個記憶體區就會分割成兩個大部分:低地址區域會產生很多佔用塊;高地址區域還是空閒塊。如圖 1 所示:


圖 1 動態分配過程中的記憶體狀態
但是當某些使用者執行結束,所佔用的記憶體區域就變成了空閒塊,如圖 2 所示:


圖 2 動態分配過程中的記憶體變化
此時,就形成了佔用塊和空閒塊犬牙交錯的狀態。當後續使用者請求分配記憶體時,系統有兩種分配方式:

  1. 系統繼續利用高地址區域的連續空閒塊分配給使用者,不去理會之前分配給使用者的記憶體區域的狀態。直到分配無法進行,也就是高地址的空閒塊不能滿足使用者的需求時,系統才會去回收之前的空閒塊,重新組織繼續分配;
  2. 當用戶執行一結束,系統馬上將其所佔空間進行回收。當有新的使用者請求分配記憶體時,系統遍歷所有的空閒塊,從中找出一個合適的空閒塊分配給使用者。
合適的空閒塊指的是能夠滿足使用者要求的空閒塊,具體的查詢方式有多種,後續會介紹。

可利用空間表

當採用第 2 種方式時,系統需要建立一張記錄所有空閒塊資訊的表。表的形式有兩種:目錄表連結串列。各自的結構如圖 3 所示:


圖 3 目錄表和連結串列 目錄表:表中每一行代表一個空閒塊,由三部分組成:

  • 初始地址:記錄每個空閒塊的起始地址。
  • 空閒塊大小:記錄每個空閒塊的記憶體大小。
  • 使用情況:記錄每個空閒塊是否儲存被佔用的狀態。

連結串列:
表中每個結點代表一個空閒塊,每個結點中需要記錄空閒塊的使用情況、大小和連線下一個空閒塊的指標域。
由於連結串列中有指標的存在,所以結點中不需要記錄各記憶體塊的起始地址。
系統在不同的環境中執行,根據使用者申請空間的不同,儲存空閒塊的可利用空間表有以下不同的結構:

  1. 如果每次使用者請求的儲存空間大小相同,對於此類系統中的記憶體來說,在使用者執行初期就將整個記憶體儲存塊按照所需大小進行分割,然後通過連結串列連結。當用戶申請空間時,從連結串列中摘除一個結點歸其使用;用完後再連結到可利用空間表上。
  2. 每次如果使用者申請的都是若干種大小規格的儲存空間,針對這種情況可以建立若干個可利用空間表,每一個連結串列中的結點大小相同。當用戶申請某一規格大小的儲存空間時,就從對應的連結串列中摘除一個結點供其使用;用完後連結到相同規格大小的連結串列中。
  3. 使用者申請的記憶體的大小不固定,所以造成系統分配的記憶體塊的大小也不確定,回收時,連結到可利用空間表中每個結點的大小也各不一樣。

第 2 種情況下容易面臨的問題是:如果同用戶申請空間大小相同的連結串列中沒有結點時,就需要找結點更大的連結串列,從中取出一個結點,一部分給使用者使用,剩餘部分插入到相應大小的連結串列中;回收時,將釋放的空閒塊插入到大小相同的連結串列中去。如果沒有比使用者申請的記憶體空間相等甚至更大的結點時,就需要系統重新組織一些小的連續空間,然後給使用者使用。

分配儲存空間的方式

通常情況下系統中的可利用空間表是第 3 種情況。如圖 3(C) 所示。由於連結串列中各結點的大小不一,在使用者申請記憶體空間時,就需要從可利用空間表中找出一個合適的結點,有三種查詢的方法:

  • 首次擬合法:在可利用空間表中從頭開始依次遍歷,將找到的第一個記憶體不小於使用者申請空間的結點分配給使用者,剩餘空間仍留在連結串列中;回收時只要將釋放的空閒塊插入在連結串列的表頭即可。
  • 最佳擬合法:和首次擬合法不同,最佳擬合法是選擇一塊記憶體空間不小於使用者申請空間,但是卻最接近的一個結點分配給使用者。為了實現這個方法,首先要將連結串列中的各個結點按照儲存空間的大小進行從小到大排序,由此,在遍歷的過程中只需要找到第一塊大於使用者申請空間的結點即可進行分配;使用者執行完成後,需要將空閒塊根據其自身的大小插入到連結串列的相應位置。
  • 最差擬合法:和最佳擬合法正好相反,該方法是在不小於使用者申請空間的所有結點中,篩選出儲存空間最大的結點,從該結點的記憶體空間中提取出相應的空間給使用者使用。為了實現這一方法,可以在開始前先將可利用空間表中的結點按照儲存空間大小從大到小進行排序,第一個結點自然就是最大的結點。回收空間時,同樣將釋放的空閒塊插入到相應的位置上。

以上三種方法各有所長:

  • 最佳擬合法由於每次分配相差不大的結點給使用者使用,所以會生成很多儲存空間特別小的結點,以至於根本無法使用,使用過程中,連結串列中的結點儲存大小發生兩極分化,大的很大,小的很小。該方法適用於申請記憶體大小範圍較廣的系統
  • 最差擬合法,由於每次都是從儲存空間最大的結點中分配給使用者空間,所以連結串列中的結點大小不會起伏太大。依次適用於申請分配記憶體空間較窄的系統。
  • 首次擬合法每次都是隨機分配。在不清楚使用者申請空間大小的情況下,使用該方法分配空間。

同時,三種方法中,最佳擬合法相比於其它兩種方式,無論是分配過程還是回收過程,都需要遍歷連結串列,所有最費時間。

空間分配與回收過程產生的問題

無論使用以上三種分配方式中的哪一種,最終記憶體空間都會成為一個一個特別小的記憶體空間,對於使用者申請的空間的需求,單獨拿出任何一個結點都不能夠滿足。

但是並不是說整個記憶體空間就不夠使用者使用。在這種情況下,就需要系統在回收的過程考慮將地址相鄰的空閒塊合併。

合併的具體方法會在後面章節詳細介紹。