C語言動態記憶體管理

2021-07-03 05:03:53 字數 2763 閱讀 1292

1-概述

動態儲存管理的基本問題是:系統如何按請求分配記憶體,如何**記憶體再利用。提出請求的使用者可能是系統的乙個作業,也可能是程式中的乙個變數。

空閒塊

未曾分配的位址連續的記憶體區稱為「空閒塊」。

占用塊

已分配給使用者使用的位址連續的記憶體區稱為「占用塊」。

系統剛剛啟動時,整個記憶體可看做乙個大的「空閒塊」,隨著使用者請求的進入,系統依次分配相應的記憶體。

在系統執行過程中,記憶體被分為兩大部分:低位址區(若干占用塊)和高位址區(空閒塊)。

經過一段時間後,有的程式執行結束,釋放掉它所占用的記憶體,使之變為空閒塊,這就使整個記憶體中空閒塊和占用塊之間出現了相互交錯的現象。如下圖:

當系統進入到圖中b),又有新的使用者請求分配記憶體時,系統該如何處理呢?

方法一:系統繼續從高位址區的空閒塊進行分配,直到無法分配。當剩餘的空閒塊不能滿足分配請求時,系統才去**所有的不再使用的記憶體區,並重新組織記憶體,緊湊所有的空閒塊為乙個大的空閒塊,以備在分配。

方法二:空閒鍊錶。空閒鍊錶中包含了所有空閒塊的資訊,乙個節點對應乙個空閒塊。當使用者請求分配時,系統所作的工作就是搜尋空閒鍊錶,按某種策略找到乙個合適的空閒塊進行分配,並刪除對應的節點。當使用者釋放所占用的記憶體時,系統**該記憶體,並將它插入到空閒鍊錶中。

空閒鍊錶

空閒鍊錶三種結構形式:

(1)所有請求的記憶體大小相同。這是一種最簡單的動態儲存管理方式。

對此,系統通常的做法是:

a)系統啟動時,將記憶體按大小均分成若干個塊,並形成乙個鍊錶。

b)分配時,只需將鍊錶中第乙個節點分配給使用者即可,無需掃瞄整個鍊錶。

c)**時,將空閒塊插入到煉表頭即可。

(2)所有請求的記憶體大小有n種規格。可建立n個(1)情況中的鍊錶,分配與**與(1)類似,不同之處在於:當請求的鍊錶為空時,需要在節點大的鍊錶上進行分配,取一部分記憶體給使用者,剩餘部分作為乙個新節點插入到相應的鍊錶中。

(3)所有請求的記憶體大小是不同的,是不斷發生變化的。這種情況下的分配與**見下篇。

分配演算法和**

對於記憶體塊大小不同的空閒鍊錶,假如有乙個大小為n的記憶體申請,如果空閒鍊錶中大於n的記憶體只有一塊,假如大小為m,就把m的一部分分配給使用者,同時把剩餘的m-n的部分作為乙個節點插入到空閒鍊錶中。

當大於n的記憶體有多塊時,我們該如何分配呢?通常有以下三種方法:

(1)最先適配法。顧名思義,順序掃瞄空閒鍊錶,找到第乙個大於等於n的空閒塊,把其中的n分配給使用者。因為鍊錶不按記憶體的位址有序,也不按大小有序,所以**記憶體塊放到表頭即可。

問題:執行一段時間後,鍊錶中將會出現一些非常小的塊,並在這些小塊都在鍊錶的前邊。

(2)最優適配法。在空閒鍊錶中找到乙個不小於n並且最接近n的空閒塊分配給使用者。分配時需要整表掃瞄。如果鍊錶按空閒塊從小到大有序,分配時只需找到第乙個大於n的空閒塊,**時需要插入到鍊錶合適的位置上。

(3)最差適配法。將鍊錶中不小於n且是鍊錶中最大的空閒塊的一部分分配給使用者。鍊錶可按空閒塊從大到小的順序排列,分配只需判斷鍊錶第乙個空閒塊即可,**時需要插入到鍊錶合適的位置上。

對於**,我們考慮的不僅僅是歸還節點,還要考慮空閒塊的合併問題。這種把實體地址相鄰的空閒塊的合併以取得更大的空閒塊的方法是需要的。空閒鍊錶中的空閒塊並不是按實體地址有序的,那又如何快速合併呢?下篇中將討論一種方法:邊界標識法

邊界標識法

就空閒鍊錶來說,要確定哪些空閒塊是實體地址相鄰的,確實不太容易,需要整表掃瞄。

邊界標識法能夠有效的判斷空閒塊的物理相鄰記憶體塊是否為空閒塊。

所有的空閒塊組成乙個雙向迴圈鍊錶,鍊錶中每乙個節點的結構如下:

llink和rlink分別指向鍊錶中的前驅後繼節點。

size表明記憶體塊的大小。

uplink指向本塊的首位址,圖中箭頭,其作用下面講述。

tag頭和尾中各有乙個,作為記憶體塊邊界的標識,0表空閒塊,1表占用塊。

分配時同樣會在鍊錶上產生很多非常小的空閒塊,影響分配的效率,因此,可以定義乙個全域性的指標p,當不為空時分配,使其始終指向剛進行過分配的節點的後繼節點。

**時判斷物理相鄰的塊是否為空閒塊的方法:

假設當前**塊的實體地址為p(下面涉及到的左鄰塊和右鄰塊都是實體地址相鄰的,在迴圈鍊錶中也許不相鄰),

左鄰塊:左鄰塊的尾的位址是p1=p-sizeof(尾),左鄰塊是否為空閒塊的判斷則為p1->tag是否為0。當為空閒塊時,只需將當前塊合併到左鄰塊中即可,而左鄰塊的位址則需要由uplink來獲得(原因是中間記憶體塊的大小是不固定的,不能通過偏移來實現),即p1->uplink。修改左鄰塊的size和uplink即可。

右鄰塊:右鄰塊的首的位址為p2=p+sizeof(頭),右鄰塊是否為空閒塊的判斷則為p2->tag是否為0。當為空閒塊時,與右鄰塊合併,修改p的llink、rlink和size,修改p2的uplink。

你可能會認為,既然鍊錶中的塊都是空閒塊,幹嗎還要加上tag標識位呢?我的回答是,如果沒有tag,當我們找到右鄰塊的位址時,沒有辦法判斷右鄰塊是不是鍊錶中的乙個節點,也許它還在被使用者使用著。那為什麼要用兩個tag呢?個人覺得,用乙個也可以,但用兩個更容易判斷。

C語言動態記憶體管理

c系統的函式庫中提供了了程式動態申請和釋放記憶體儲存塊的庫函式,下面將分別介紹。1 malloc 函式 a 該函式的原型 void malloc size t size b 該函式只有乙個引數,且形參size是無符號整型,該引數代表申請空間的位元組數。c 返回值 如果記憶體池中的可用記憶體滿足需求,...

C語言動態記憶體管理

在說明c語言記憶體管理之前,要知道什麼是記憶體,記憶體我個人認為可以理解為帶有標籤的盒子,所謂的帶標籤的盒子就像我們住的寢室一樣有門牌號,盒子內只能儲存固定型別的資料或變數,就如男生寢室只能住男生一樣。那麼c語言中有多少種盒子呢?有靜態儲存區 動態儲存區 內部暫存器區域。我們通常定義的變數如果沒有特...

C 語言動態記憶體管理

int a 10 在棧空間開闢了4個位元組 int arr 10 在棧空間連續開闢了10個連續的4位元組空間 上面的兩行 開闢空間的方式均是開闢了大小固定的空間。但是我們實際對空間的需求,不僅僅是上訴的情況。而是隨著程式執行的時候我們才知道我們需要的空間要多大,那陣列的編譯時開闢空間的方式就不能買滿...