1. 程式人生 > >new與malloc的區別以及實現方法

new與malloc的區別以及實現方法

new和malloc的記憶體分配在哪

分配在堆上。也有說new是分配在自由儲存區而malloc分配在堆上,自由儲存區可以是堆也可以不是,具體要看new內部的實現。作業系統在堆上維護一個空閒記憶體連結串列,當需要分配記憶體的時候,就查詢這個表,找到一塊記憶體大於所需記憶體的區域,分配記憶體並將剩餘的記憶體空間返還到空閒連結串列上(如果有剩餘的話)。

new/delete和malloc/free的區別

1. malloc和free是庫函式,而new和delete是C++操作符;

2. new自己計算需要的空間大小,比如’int * a = new,malloc需要指定大小,例如’int * a = malloc(sizeof(int))’;

3. new在動態分配記憶體的時候可以初始化物件,呼叫其建構函式,delete在釋放記憶體時呼叫物件的解構函式。而malloc只分配一段給定大小的記憶體,並返回該記憶體首地址指標,如果失敗,返回NULL。

4. new是C++操作符,是關鍵字,而operate new是C++庫函式

5. opeartor new /operator delete可以過載,而malloc不行

6. new可以呼叫malloc來實現,但是malloc不能呼叫new來實現

7. 對於資料C++定義new[]專門進行動態陣列分配,用delete[]進行銷燬。new[]會一次分配記憶體,然後多次呼叫建構函式;delete[]會先多次呼叫解構函式,然後一次性釋放。

分配陣列不同之處
int char* pa = new char[100];
int char* pb = malloc(sizeof(char) * 100);

8. malloc能夠直觀地重新分配記憶體

使用malloc分配的記憶體後,如果在使用過程中發現記憶體不足,可以使用realloc函式進行記憶體重新分配實現記憶體的擴充。realloc先判斷當前的指標所指記憶體是否有足夠的連續空間,如果有,原地擴大可分配的記憶體地址,並且返回原來的地址指標;如果空間不夠,先按照新指定的大小分配空間,將原有資料從頭到尾拷貝到新分配的記憶體區域,而後釋放原來的記憶體區域。
new沒有這樣直觀的配套設施來擴充記憶體。

new和malloc內部實現的區別

new

以下是從網上找來的一段關於new的程式碼,不知道和標準的實現是否有區別,但是原理應該是這樣,足夠來說明問題了:

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)  
      {       // try to allocate size bytes  
      void *p;  
      while ((p = malloc(size)) == 0)  
              if (_callnewh(size) == 0)  
              {       // report no memory  
                      _THROW_NCEE(_XSTD bad_alloc, );
              }  

     return (p);  
     } 

new: 可以理解成兩步:
1. 呼叫operate new()分配記憶體,如果記憶體不足失敗,丟擲異常;
2. 如果需要的話,在那段記憶體上初始化物件(賦值或者呼叫建構函式),這個應該是由編譯器根據程式碼來控制的。

因此對於new和malloc檢查是否正確分配的方法是不一樣的

int *a  = (int *)malloc ( sizeof (int ));
if(NULL == a)
{
    ...
}
else 
{
    ...
}
從C語言走入C++陣營的新手可能會把這個習慣帶入C++:

int * a = new int();
if(NULL == a)
{
    ...
}
else
{   
    ...
}
實際上這樣做一點意義也沒有,因為new根本不會返回NULL,而且程式能夠執行到if語句已經說明記憶體分配成功了,如果失敗早就拋異常了,後面的程式碼就不會執行了。正確的做法應該是使用異常機制:

try
{
    int *a = new int();
}
catch (std::bad_alloc& e)
{
    ...
}

為了照顧原來習慣的程式設計師,C++可以通過nothrow關鍵字來實現new不拋異常而是返回NULL。

int* p = new(std::nothrow) int;

malloc

參考網上多篇文章,自己寫了一個malloc,也沒測過,如有錯誤歡迎指正。但是,用來說明malloc的原理的我想是沒問題的。

#define malloc_addr 0x00000
#define malloc_size 0x22222

#ifndef NULL
#define NULL 0
#endif

void* managed_memory_start = NULL;  //堆區的起始地址
void* managed_memory_end = NULL;    //堆取的終止地址
int is_initialized = 0;

/*
 * 記憶體控制塊,通過記憶體控制塊將堆區的記憶體用雙向連結串列連線起來管理
 */
typedef struct 
{
    unsigned int is_available;
    unsigned int current_block_size;
    unsigned int prev_block_size;
}mem_control_block;


void malloc_init(void)
{
    mem_control_block* tmp = NULL;
    managed_memory_start = (void*)malloc_addr;
    managed_memory_end = (void*)(malloc_addr + malloc_size);

    tmp = (mem_control_block*)managed_memory_start;
    tmp->is_available = 1;
    tmp->current_block_size = (managed_memory_end - managed_memory_start) - sizeof(mem_control_block);
    tmp->prev_block_size = 0;
    is_initialized = 1;
}

void* malloc(size_t size)
{
    //初始化,最開始連結串列只有一個節點
    if(!is_initialized)
    {
        malloc_init();
    }

    //儲存記憶體地址遊標
    void* current_location = NULL;
    //儲存當前記憶體控制塊的位置
    mem_control_block* current_location_mcb = NULL;
    //如果塊太大,取下所需大小,剩餘放回連結串列
    mem_control_block* leave_location_mcb = NULL;
    //定義一個用於返回的指標,返回的地址是控制塊加上前面結構體的大小
    void* memory_location = NULL;

    //把遊標指向堆的首地址
    current_location = managed_memory_start;

    while(current_location <= managed_memory_end)
    {
        current_location_mcb = (mem_control_block*)current_location;
        //判斷該節點是否被空閒
        if(current_location_mcb->is_available)
        {
            //判斷該空閒塊大於需要,但是剩下的空間又不足以維持一個空閒塊,就把整個塊都分配了,為了維持連結串列的連續性
            //實際上也浪費不了多少記憶體,因為一個struct結構體很小
            if(current_location_mcb->current_block_size < size + 2 * sizeof(mem_control_block))
            {
                current_location_mcb->is_available = 0;
                break;
            }
            else //如果空閒塊大於需要的,就把剩餘的塊放入leave_location_mcb節點了
            {
                unsigned int process_blocksize;

                //另當前塊為被使用狀態
                current_location_mcb->is_available = 0;
                process_blocksize = current_location_mcb->current_block_size;

                current_location_mcb->current_block_size = size + sizeof(mem_control_block);
                leave_location_mcb = (mem_control_block*)(current_location + process_blocksize);
                leave_location_mcb->is_available = 1;
                //當前塊大小減去需要的記憶體減去一個控制塊結構體就是剩餘的大小
                leave_location_mcb->current_block_size =  process_blocksize - sizeof(mem_control_block) - size;
                //leaveo塊的前一個塊的大小就是要分配的大小
                leave_location_mcb->prev_block_size = size + sizeof(mem_control_block);
            }
        }
        //如果該塊不空閒,則指標遊標指向下一塊的首地址
        current_location += current_location_mcb->current_block_size;
    }

    //如果空閒連結串列中已經沒有合適的塊就擴大堆區的範圍
    if(!memory_location)
    {
        //申請取擴大堆取的記憶體
        if(sbrk(size + sizeof(mem_control_block)) != -1)
            return NULL;

        //如果空閒連結串列中沒有合適的塊,那麼必然是遍歷了整個連結串列,此時的current_location_mcb指向原來空閒連結串列的最後一個塊
        //將該塊的大小儲存下來,就是馬上要分配塊的前一個塊的大小了
        unsigned int prev_size = current_location_mcb->current_block_size;

        //之前的迴圈會使得current_location指向最後一個塊末尾的下一個地址
        current_location_mcb = (mem_control_block*)current_location;
        current_location_mcb->current_block_size = size + sizeof(mem_control_block);
        current_location_mcb->prev_block_size = prev_size;
    }

    memory_location = current_location + sizeof(mem_control_block);
    return memory_location;
}

系統為堆區儲存了兩個指標一個指向堆的首地址,一個指向堆的尾地址(我猜想這兩個指標是不是就是有vm_area_struct裡的兩個指標維護的,原始碼還沒看到只是猜測)。系統通過結構體mem_control_block將整個堆區分成一個個塊,每個塊都已這樣一個結構體開頭,結構體裡維護了塊的大小。malloc分配記憶體的時候,從第一個塊開始遍歷,如果找到了塊已經被使用,那麼就找下一個塊,如果找到的塊比需要的小繼續找下一個塊,直到找到比我需要大於等於的塊,然後將該塊mem_control_block中的標誌位設定為被使用了,如果塊有剩餘的就將剩餘的空間新增一個mem_control_block變成一個新塊。如果遍歷了整個堆空間都沒有找到合適的塊,那麼就呼叫sbrk函式擴大堆的範圍。

brk() and sbrk() change the location of the program break, which defines the end of the process’s data segment (i.e., the program break is the first location after the end of the uninitialized data segment). Increasing the program break has the effect of allocating memory to the process; decreasing the break deallocates memory.

brk() sets the end of the data segment to the value specified by addr, when that value is reasonable, the system has enough memory, and the process does not exceed its maxi‐mum data size (see setrlimit(2)).

sbrk()increments the program’s data space by increment bytes. Calling sbrk() with an increment of 0 can be used to find the current location of the program break.

上面這段摘自Linux手冊,brk函式和sbrk都是擴大堆區(program break就是堆,因為文中說了program break就是bss後的第一個位置)尾地址的。只不過brk通過指定一個地址,而sbrk通過追加一個大小。

通過上面的程式碼我們可以直到當我們呼叫malloc分配n個位元組的空間時,實際上佔用了n + sizeof(mem_control_block)個位元組的空間。但是返回的地址是緊跟結構體mem_control_block後面的第一個地址。那麼相信聰明的你已經猜到了free是如何釋放記憶體的了。沒錯,就是把指標往回倒個sizeof(mem_control_block)的距離,然後設定標誌位為可用。為了減少記憶體碎片,會取查詢前面一個塊和後面一個塊,如果有空閒塊就合併為一個。

給free()中的引數就可以完成釋放工作!這裡要追蹤到malloc()的申請問題了。申請的時候實際上佔用的記憶體要比申請的大。因為超出的空間是用來記錄對這塊記憶體的管理資訊。先看一下在《UNIX環境高階程式設計》中第七章的一段話:

大多數實現所分配的儲存空間比所要求的要稍大一些,額外的空間用來記錄管理資訊——分配塊的長度,指向下一個分配塊的指標等等。這就意味著如果寫過一個已分配區的尾端,則會改寫後一塊的管理資訊。這種型別的錯誤是災難性的,但是因為這種錯誤不會很快就暴露出來,所以也就很難發現。將指向分配塊的指標向後移動也可能會改寫本塊的管理資訊。

參考:

文章1

文章2

文章3