C語言動態內存分配:(一)malloc/free的實現及malloc實際分配/釋放的內存

1、malloc/free概述安全

malloc是在C語言中用於在程序運行時在堆中進行動態內存分配的庫函數。free是進行內存釋放的庫函數。函數

一、函數原型編碼

#include <stdlib.h>
 
void *malloc( 
   size_t size 
);
void free( 
   void* memblock 
);
二、返回值
成功時,返回所分配存儲空間的起始地址;返回值類型爲void*,在C語言中能夠把void*直接付給具體的類型,可是在C++中必須進行強制類型轉換spa

失敗時(內存不足時)返回NULL操作系統

size爲0時,返回的指針不是NULL;可是除了free,別的地方不要使用這個指針。.net

三、使用示例設計

#include <stdlib.h>         /* For _MAX_PATH definition */
#include <stdio.h>
#include <malloc.h>
 
void main( void )
{
   char *string;
 
   /* Allocate space for a path name */
   string = malloc( _MAX_PATH );
 
   // In a C++ file, explicitly cast malloc's return.  For example, 
   // string = (char *)malloc( _MAX_PATH );
 
   if( string == NULL )
      printf( "Insufficient memory available\n" );
   else
   {
      printf( "Memory space allocated for path name\n" );
      free( string );
      printf( "Memory freed\n" );
   }
}指針

2、malloc實際分配的內存大小blog

   malloc實際分配的內存會大於咱們須要的size。主要由兩方面因素決定:內存

一、字節對齊。會對齊到機器最受限的類型(具體的實現因機器而異)。

二、「塊頭部信息」。每一個空閒塊都有「頭部」控制信息,其中包含一個指向鏈表中下一個塊的指針、當前塊的大小和一個指向自己的指針。爲了簡化塊對齊,全部塊的大小都必須是頭部大小的整數倍,且頭部已正確對齊。

在VC平臺下由_CrtMemBlockHeader結構體實現。

如下爲《C程序設計語言》中給出的經過union進行的頭部實現,其中假定機器的受限類型爲long。

typedef long Align;/*按照long類型的邊界對齊*/
union header/*塊的頭部*/
{
    struct
    {
        union header *ptr;/*空閒塊鏈表中的下一塊*/
        unsigned size;/*本塊的大小*/
    }s;
    Align x;/*強制塊對齊*/
};
說明:
(1)實際分配的內存塊將多一個單元,用於頭部自己。實際分配的塊的大小被記錄在頭部的size字段中。
(2)size字段是必須的,由於malloc函數控制的塊不必定是連續的,這樣就不能經過指針算術運算計算其大小。

(3)malloc返回的是空閒塊的首地址,而不是首地址。

3、malloc/free實現過程

   一、空閒存儲空間以空閒鏈表的方式組織(地址遞增),每一個塊包含一個長度、一個指向下一塊的指針以及一個指向自身存儲空間的指針。( 由於程序中的某些地方可能不經過malloc調用申請,所以malloc管理的空間不必定連續。)

  二、當有申請請求時,malloc會掃描空閒鏈表,直到找到一個足夠大的塊爲止(首次適應)(所以每次調用malloc時並非花費了徹底相同的時間)。

  三、若是該塊剛好與請求的大小相符,則將其從鏈表中移走並返回給用戶。若是該塊太大,則將其分爲兩部分,尾部的部分分給用戶,剩下的部分留在空閒鏈表中(更改頭部信息)。所以malloc分配的是一塊連續的內存。

  四、釋放時,首先搜索空閒鏈表,找到能夠插入被釋放塊的合適位置。若是與被釋放塊相鄰的任一邊是一個空閒塊,則將這兩個塊合爲一個更大的塊,以減小內存碎片。

4、實現

如下爲《C語言程序設計語言》中給出的一種實現方法

一、malloc的實現

typedef union header Header;
static Header base;/*從空鏈表開始*/
static Header *freep = NULL;/*空閒鏈表的初始指針*/
 
void *malloc(unsigned nbytes)
{
    Header *p, *prevp;
    Header *morecore(unsigned);
    unsigned nunits;
 
    nunits = (nbytes+sizeof(Header)-1)/sizeof(Header) + 1;
    if((prevp = freep) == NULL) /* 沒有空閒鏈表 */
    { 
        base.s.ptr = freep = prevp = &base;
        base.s.size = 0;
    }
    for(p = prevp->s.ptr; ;prevp = p, p= p->s.ptr) 
    {
        if(p->s.size >= nunits) /* 足夠大 */
        { 
            if (p->s.size == nunits)  /* 正好 */
                prevp->s.ptr = p->s.ptr;
            else  /*分配末尾部分*/
            {                 
                p->s.size -= nunits;
                p += p->s.size;
                p->s.size = nunits;
            }
            freep = prevp;
            return (void*)(p+1);
        }
        if (p== freep) /* 閉環的空閒鏈表*/
            if ((p = morecore(nunits)) == NULL)
                return NULL; /* 沒有剩餘的存儲空間 */
    }
}
說明:

(1)malloc實際分配的空間是Header大小的整數倍,而且多出一個Header空間用於放置Header

(2)式nunits = (nbytes+sizeof(Header)-1)/sizeof(Header) + 1中的減1是爲了防止(nbytes+sizeof(Header))%sizeof(Header) == 0時,多分配了一個Header大小的空間

(3)第一次調用malloc函數時,freep爲NULL,系統將建立一個退化的空閒鏈表,它只包含一個大小爲0的塊,且該塊指向本身。任什麼時候候,當請求空閒空間時,都將搜索空閒塊鏈表。搜索從上一次找到空閒塊的地方(freep)開始。該策略能夠保證鏈表是均勻的。若是找到的塊太大,則將其尾部返回給用戶,這樣,初始塊的頭部只須要修改size字段便可。

(4)任什麼時候候,返回給用戶的指針都指向塊內的空閒存儲空間,即比指向頭部的指針大一個單元。

(5)sbrk不是系統調用,是C庫函數。sbrk/brk是從堆中分配空間,本質是移動一個位置,向後移就是分配空間,向前移就是釋放空間,sbrk用相對的整數值肯定位置,若是這個整數是正數,會從當前位置向後移若干字節,若是爲負數就向前若干字節。在任何狀況下,返回值永遠是移動以前的位置。在LINUX中sbrk(0)能返回比較精確的虛擬內存使用狀況。連接:sbrk()/brk()--改變數據長度

(6)連接:brk()分配過程

二、morecore的實現

    函數morecore用來向操做系統請求存儲空間,其實現細節因系統的不一樣而不一樣。由於向系統請求存儲空間是一個開銷很大的操做,所以咱們不但願每次調用malloc時都執行該操做,基於這個考慮,morecore函數請求至少NALLOC個單元。這個較大的塊將根據須要分紅較小的塊。在設置完size字段後,morecore函數調用free函數把多餘的存儲空間插入到空閒區域中。

#define NALLOC 1024    /* 最小申請單元數 */
static Header *morecore(unsigned nu)
{
    char *cp;
    Header *up;
    if(nu < NALLOC)
        nu = NALLOC;
    cp = sbrk(nu * sizeof(Header));
    if(cp == (char *)-1)    /* 沒有空間*/
        return NULL;
    up = (Header *)cp;
    up->s.size = nu;
    free((void *)(up+1));
    return freep;
}
說明:

(1)沒有存儲空間時sbrk調用返回-1.所以須要將-1強制類型轉換爲char*類型,以便 與返回值進行比較。並且,強制類型轉換使得函數不會受不一樣機器中指針表示的不一樣的影響。

三、free的實現

     free函數從freep指向的地址開始,逐個掃描空閒鏈表,尋找能夠插入空閒塊的地方。該位置可能在鏈表的末尾或者兩個空閒塊之間。在任何一種狀況下,若是被釋放的塊與另外一空閒塊相鄰,則將這兩個塊合併。

void free(void *ap)
{
    Header *bp,*p;
    bp = (Header *)ap -1; /* 指向塊頭 */
    for(p=freep;!(bp>p && bp< p->s.ptr);p=p->s.ptr)
        if(p>=p->s.ptr && (bp>p || bp<p->s.ptr))
            break;    /* 被釋放的塊在鏈表的開頭或結尾*/
    if (bp+bp->s.size==p->s.ptr) /*與上一相鄰塊合併 */
    {    
        bp->s.size += p->s.ptr->s.size;
        bp->s.ptr = p->s.ptr->s.ptr;
    } 
    else
        bp->s.ptr = p->s.ptr;
    if (p+p->s.size == bp)/* 與下一相鄰塊合併 */ 
    {     
        p->s.size += bp->s.size;
        p->s.ptr = bp->s.ptr;
    } 
    else
        p->s.ptr = bp;
    freep = p;
}
5、注意事項

連接:malloc/free使用注意事項

參考文獻:

一、《C程序設計語言》

二、《C和指針》

三、《C和C++安全編碼》

四、連接:如何實現一個malloc

———————————————— 版權聲明:本文爲CSDN博主「Z-H-I」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接及本聲明。 原文連接:https://blog.csdn.net/zxx910509/article/details/62881131

相關文章
相關標籤/搜索