1. 程式人生 > >malloc函式的一種簡單的原理性實現

malloc函式的一種簡單的原理性實現

malloc()是C語言中動態儲存管理的一組標準庫函式之一。其作用是在記憶體的動態儲存區中分配一個長度為size的連續空間。其引數是一個無符號整形數,返回值是一個指向所分配的連續儲存域的起始地址的指標

malloc()工作機制

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

malloc()在作業系統中的實現

  在 C 程式中,多次使用malloc () 和 free()。不過,您可能沒有用一些時間去思考它們在您的作業系統中是如何實現的。本節將向您展示 malloc 和 free 的一個最簡化實現的程式碼,來幫助說明管理記憶體時都涉及到了哪些事情。
  在大部分作業系統中,記憶體分配由以下兩個簡單的函式來處理:
  void *malloc (long numbytes):該函式負責分配 numbytes 大小的記憶體,並返回指向第一個位元組的指標。
  void free(void *firstbyte):如果給定一個由先前的 malloc 返回的指標,那麼該函式會將分配的空間歸還給程序的“空閒空間”。

  malloc_init 將是初始化記憶體分配程式的函式。它要完成以下三件事:將分配程式標識為已經初始化,找到系統中最後一個有效記憶體地址,然後建立起指向我們管理的記憶體的指標。這三個變數都是全域性變數:



//清單 1. 我們的簡單分配程式的全域性變數

        
int has_initialized =0;
        
void*managed_memory_start;
        
void*last_valid_address;

如前所述,被對映的記憶體的邊界(最後一個有效地址)常被稱為系統中斷點或者 當前中斷點。在很多 UNIX? 系統中,為了指出當前系統中斷點,必須使用 sbrk(0) 函式。 sbrk 根據引數中給出的位元組數移動當前系統中斷點,然後返回新的系統中斷點。使用引數 0 只是返回當前中斷點。這裡是我們的 malloc 初始化程式碼,它將找到當前中斷點並初始化我們的變數:



清單 2. 分配程式初始化函式
/* Include the sbrk function */
 
#include 
void malloc_init()
{
/* grab the last valid address from the OS */
last_valid_address 
= sbrk(0);
/* we don''t have any memory to manage yet, so
 *just set the beginning to be last_valid_address
 
*/
managed_memory_start 
= last_valid_address;
/* Okay, we''re initialized and ready to go */
 has_initialized 
=1;
}

現在,為了完全地管理記憶體,我們需要能夠追蹤要分配和回收哪些記憶體。在對記憶體塊進行了 free 呼叫之後,我們需要做的是諸如將它們標記為未被使用的等事情,並且,在呼叫 malloc 時,我們要能夠定位未被使用的記憶體塊。因此, malloc 返回的每塊記憶體的起始處首先要有這個結構:



//清單 3. 記憶體控制塊結構定義struct mem_control_block {
    
int is_available;
    
int size;
};

現在,您可能會認為當程式呼叫 malloc 時這會引發問題 —— 它們如何知道這個結構?答案是它們不必知道;在返回指標之前,我們會將其移動到這個結構之後,把它隱藏起來。這使得返回的指標指向沒有用於任何其他用途的記憶體。那樣,從呼叫程式的角度來看,它們所得到的全部是空閒的、開放的記憶體。然後,當通過 free() 將該指標傳遞回來時,我們只需要倒退幾個記憶體位元組就可以再次找到這個結構。

  在討論分配記憶體之前,我們將先討論釋放,因為它更簡單。為了釋放記憶體,我們必須要做的惟一一件事情就是,獲得我們給出的指標,回退 sizeof(struct mem_control_block) 個位元組,並將其標記為可用的。這裡是對應的程式碼:



清單 4. 解除分配函式
void free(void*firstbyte) {
    
struct mem_control_block *mcb;
/* Backup from the given pointer to find the
 * mem_control_block
 
*/
   mcb 
= firstbyte -sizeof(struct mem_control_block);
/* Mark the block as being available */
  mcb
->is_available =1;
/* That''s It!  We''re done. */return;
}

如您所見,在這個分配程式中,記憶體的釋放使用了一個非常簡單的機制,在固定時間內完成記憶體釋放。分配記憶體稍微困難一些。我們主要使用連線的指標遍歷記憶體來尋找開放的記憶體塊。這裡是程式碼:



//清單 6. 主分配程式void*malloc(long numbytes) {
    
/* Holds where we are looking in memory */void*current_location;
    
/* This is the same as current_location, but cast to a
    * memory_control_block
    
*/struct mem_control_block *current_location_mcb;
    
/* This is the memory location we will return.  It will
    * be set to 0 until we find something suitable
    
*/void*memory_location;
    
/* Initialize if we haven''t already done so */if(! has_initialized) {
        malloc_init();
    }
    
/* The memory we search for has to include the memory
    * control block, but the users of malloc don''t need
    * to know this, so we''ll just add it in for them.
    
*/
    numbytes 
= numbytes +sizeof(struct mem_control_block);
    
/* Set memory_location to 0 until we find a suitable
    * location
    
*/
    memory_location 
=0;
    
/* Begin searching at the start of managed memory */
    current_location 
= managed_memory_start;
    
/* Keep going until we have searched all allocated space */while(current_location != last_valid_address)
    {
    
/* current_location and current_location_mcb point
    * to the same address.  However, current_location_mcb
    * is of the correct type, so we can use it as a struct.
    * current_location is a void pointer so we can use it
    * to calculate addresses.
        
*/
        current_location_mcb 
=
            (
struct mem_control_block *)current_location;
        
if(current_location_mcb->is_available)
        {
            
if(current_location_mcb->size >= numbytes)
            {
            
/* Woohoo!  We''ve found an open,
            * appropriately-size location.
                
*//* It is no longer available */
                current_location_mcb
->is_available =0;
                
/* We own it */
                memory_location 
= current_location;
                
/* Leave the loop */break;
            }
        }
        
/* If we made it here, it''s because the Current memory
        * block not suitable; move to the next one
        
*/
        current_location 
= current_location +
            current_location_mcb
->size;
    }
    
/* If we still don''t have a valid location, we''ll
    * have to ask the operating system for more memory
    
*/if(! memory_location)
    {
        
/* Move the program break numbytes further */
        sbrk(numbytes);
        
/* The new memory will be where the last valid
        * address left off
        
*/
        memory_location 
= last_valid_address;
        
/* We''ll move the last valid address forward
        * numbytes
        
*/
        last_valid_address 
= last_valid_address + numbytes;
        
/* We need to initialize the mem_control_block */
        current_location_mcb 
= memory_location;
        current_location_mcb
->is_available =0;
        current_location_mcb
->size = numbytes;
    }
    
/* Now, no matter what (well, except for error conditions),
    * memory_location has the address of the memory, including
    * the mem_control_block
    
*//* Move the pointer past the mem_control_block */
    memory_location 
= memory_location +sizeof(struct mem_control_block);
    
/* Return the pointer */return memory_location;
 }

這就是我們的記憶體管理器。現在,我們只需要構建它,並在程式中使用它即可.多次呼叫malloc()後空閒記憶體被切成很多的小記憶體片段,這就使得使用者在申請記憶體使用時,由於找不到足夠大的記憶體空間,malloc()需要進行記憶體整理,使得函式的效能越來越低。聰明的程式設計師通過總是分配大小為2的冪的記憶體塊,而最大限度地降低潛在的malloc效能喪失。也就是說,所分配的記憶體塊大小為4位元組、8位元組、16位元組、18446744073709551616位元組,等等。這樣做最大限度地減少了進入空閒鏈的怪異片段(各種尺寸的小片段都有)的數量。儘管看起來這好像浪費了空間,但也容易看出浪費的空間永遠不會超過50%。

---------------------------------------------------------------------------------------------

malloc函式 
函式宣告(函式原型): 
void *malloc(int size); 
說明:malloc 向系統申請分配指定size個位元組的記憶體空間。返回型別是 void* 型別。void* 表示未確定型別的指標。C,C++規定,void* 型別可以強制轉換為任何其它型別的指標。 
從函式宣告上可以看出。malloc 和 new 至少有兩個不同: new 返回指定型別的指標,並且可以自動計算所需要大小。比如: 
int *p; 
p = new int; //返回型別為int* 型別(整數型指標),分配大小為 sizeof(int); 
或: 
int* parr; 
parr = new int [100]; //返回型別為 int* 型別(整數型指標),分配大小為 sizeof(int) * 100; 
而 malloc 則必須由我們計算要位元組數,並且在返回後強行轉換為實際型別的指標。 
int* p; 
p = (int *) malloc (sizeof(int)); 
第一、malloc 函式返回的是 void * 型別,如果你寫成:p = malloc (sizeof(int)); 則程式無法通過編譯,報錯:“不能將 void* 賦值給 int * 型別變數”。所以必須通過 (int *) 來將強制轉換。 
第二、函式的實參為 sizeof(int) ,用於指明一個整型資料需要的大小。如果你寫成: 
int* p = (int *) malloc (1); 
程式碼也能通過編譯,但事實上只分配了1個位元組大小的記憶體空間,當你往裡頭存入一個整數,就會有3個位元組無家可歸,而直接“住進鄰居家”!造成的結果是後面的記憶體中原有資料內容全部被清空。 
malloc 也可以達到 new [] 的效果,申請出一段連續的記憶體,方法無非是指定你所需要記憶體大小。 
比如想分配100個int型別的空間: 
int* p = (int *) malloc ( sizeof(int) * 100 ); //分配可以放得下100個整數的記憶體空間。 
另外有一點不能直接看出的區別是,malloc 只管分配記憶體,並不能對所得的記憶體進行初始化,所以得到的一片新記憶體中,其值將是隨機的。 
除了分配及最後釋放的方法不一樣以外,通過malloc或new得到指標,在其它操作上保持一致。

--------------------------------------------------------------------------------------------

有了malloc/free 為什麼還要new/delete ?
malloc 與free 是C++/C 語言的標準庫函式,new/delete 是C++的運算子。它們都可
用於申請動態記憶體和釋放記憶體。
對於非內部資料型別的物件而言,光用maloc/free 無法滿足動態物件的要求。物件
在建立的同時要自動執行建構函式, 物件在消亡之前要自動執行解構函式。由於
malloc/free 是庫函式而不是運算子,不在編譯器控制權限之內,不能夠把執行建構函式
和解構函式的任務強加於malloc/free。
因此C++語言需要一個能完成動態記憶體分配和初始化工作的運算子new,以及一個
能完成清理與釋放記憶體工作的運算子delete。注意new/delete 不是庫函式。
我們先看一看malloc/free 和new/delete 如何實現物件的動態記憶體管理,見示例7-8。
class Obj
{
public :
Obj(void){ cout << “Initialization” << endl; }
~Obj(void){ cout << “Destroy” << endl; }
void Initialize(void){ cout << “Initialization” << endl; }
void Destroy(void){ cout << “Destroy” << endl; }
};
void UseMallocFree(void)
{
Obj *a = (obj *)malloc(sizeof(obj)); // 申請動態記憶體
a->Initialize(); // 初始化
//…
a->Destroy(); // 清除工作
free(a); // 釋放記憶體
}
void UseNewDelete(void)
{
Obj *a = new Obj; // 申請動態記憶體並且初始化
//…
delete a; // 清除並且釋放記憶體
}
示例7-8 用malloc/free 和new/delete 如何實現物件的動態記憶體管理
類Obj 的函式Initialize 模擬了建構函式的功能,函式Destroy 模擬了解構函式的功
能。函式UseMallocFree 中,由於malloc/free 不能執行建構函式與解構函式,必須呼叫
成員函式Initialize 和Destroy 來完成初始化與清除工作。函式UseNewDelete 則簡單得

多。
所以我們不要企圖用malloc/free 來完成動態物件的記憶體管理,應該用new/delete。
由於內部資料型別的“ 物件”沒有構造與析構的過程,對它們而言malloc/free 和
new/delete 是等價的。
既然new/delete 的功能完全覆蓋了malloc/free,為什麼C++不把malloc/free 淘
汰出局呢?這是因為C++程式經常要呼叫C 函式,而C 程式只能用malloc/free 管理動
態記憶體。
如果用free 釋放“new 建立的動態物件”,那麼該物件因無法執行解構函式而可能
導致程式出錯。如果用delete 釋放“malloc 申請的動態記憶體”,理論上講程式不會出錯,
但是該程式的可讀性很差。所以new/delete 必須配對使用,malloc/free 也一樣。