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 也一樣。

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

下面是我測試的代碼

 #include <stdlib.h>
 #include <stdio.h>
 int main()
{
   int i;
   size_t sum = 1;

   for(i = 0; ; ++i) {
     char *tmp = malloc(sum);
         free(tmp);
         sum *= 2;
   }

   return 0;
}

運行這個程序時,當sum = 2147483648時,請求內存分配時,進入malloc函數,在下面的代碼出現了問題;

在 current_location_mcb->is_available = 0;這個句代碼出現問題,
調試結果顯示,不能訪問current_location_mcb->is_available指向的內存。

請問一下,sbrk增加的內存,我們應用程序都可以訪問的嗎?會不會一些受保護的內存區域也可以通過這個函數來獲得呢?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章