C程序中的存儲分配
(劉愛貴 - Aiguille.LIU)
C程序設計中,經常需要使用malloc/free動態管理內存,在需要的時候向操作系統申請空間,適合的時候釋放不再使用的空間。那麼,C庫中malloc/free是如何實現的呢?參考"The C Programming Language",我們設計了自己的存儲分配程序。
由於程序中某些地方可能不通過malloc調用申請空間,因此,malloc管理的空間不一下是連續的。這樣,空閒存儲空間以空閒鏈表的方式組織,每個塊包含一個長度、一個指向下一塊的指針以及一個指向自身的存儲空間的指針。這些塊按照存儲地址的升序組織,最後一塊指向第一塊。
當有申請請求時,malloc將掃描空閒塊鏈表,直到找到一個足夠大的塊爲止。這種算法稱爲“首次適應(first fit)”。與之相對的算法是“最佳適應(best fit)”,它尋找滿足條件的最小塊。如果該塊恰好與請求的大小相符合,則將它從鏈表中移走並返回用戶。如果該塊太大,則將它分成兩部分:大小合適的塊返回給用戶,剩下的部分留在空閒塊鏈表中。如果找不到一個足夠大的塊,則向操作系統申請一個大塊並加入到空閒塊鏈表中。
釋放過程也是首先搜索空閒塊鏈表,以找到可以插入被釋放塊的合適位置。如果與被釋放塊相鄰的任一邊是一個空閒塊,則將這兩個塊合成一個更大的塊,這樣存儲空間不會有太多的碎片。因爲空閒塊鏈表是以地址的遞增順序鏈接在一起的,所以很容易判斷相相鄰的塊是否空閒。
malloc函數返回的存儲空閒要求滿足將要保存的對象的對齊要求。雖然機器類型各異,但是,每個特定的機器都有一個最受限的類型:如果最受限的類型可以存儲在某個特定的地址中,則其他所有的類型也可以存放在此地址中。在某些機器中,最受限的類型是double;而在另外一些機器中,最受限的類型是int或long類型。
下面的my_malloc.c程序,簡化了塊的對齊,所有塊的大小都是頭部大小的整數倍,且頭部已正確地對齊。空閒塊開始處的控制信息稱爲“頭部”。假定long類型爲最受限的類型:
#include <string.h>
#define NALLOC 1024 /* 最小申請單元數 */
typedef long Align; /**//* 按照long類型的邊界對齊 */
union header /**//* 塊的頭部 */
...{
struct ...{
union header *ptr; /**//* 空閒塊的鏈表的下一塊 */
unsigned size; /**//* 本塊的大小 */
}s;
Align x; /**//* 強制塊的對齊 */
};
typedef union header Header;
static Header base; /**//* 從空鏈表開始 */
static Header *freep = NULL; /**//* 空閒鏈表的初始指針 */
/**//* free 函數:將塊ap放入空閒塊鏈表中 */
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;
}
/**//* morecore: 向系統申請更多的存儲空間 */
static Header *morecore( unsigned nu)
...{
char *cp, *sbrk(int );
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;
}
/**//* mumalloc: 存儲分配函數 */
void *mymalloc (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 = 0, 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; /**//* 沒有剩餘的存儲空間 */
}
}
/**//* 主函數:測試mymalloc函數 */
int main(void)
...{
char * p ;
p = (char *)mymalloc (30);
strcpy(p, "hello world");
printf("%s ", p);
}
更詳細的程序說明請直接參考"The C Programming Language"一書162 ~ 166頁。