malloc并不是从一个编译时就确定的固定大小的数组中分配空间,而是在需要的时向操作系统申请空间。因为程序中的某些地方可能不通过malloc调用申请空间(通过其它方式申请空间),因此,malloc管理的空间不一定是连续的。这样空闲存储空间以空闲块链表的方式组织,每个块包含一个长度、一个指向下一块的指针以及一个指向自身存储空间的指针。
当有申请请求时,malloc将扫描空闲块链表,直到找到一个足够大的块为止。该算法称为“首次适应”(first fit);与之相对应的算法是“最佳适应”(best fit),它寻找满足条件的最小块。如果该块恰好与请求的大小相符合,则将它从链表中移走并返回给用户。如果该块太大,则将它分成两部分;大小合适的块返回给用户,剩下的部分留在空闲块链表中。如果找不到一个足够大的块,则向操作系统申请一个大块并加入到空闲块链表中。
malloc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| 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 /*空闲链表的初始指针*/
/*malloc函数:通用存储分配函数*/
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; /*没有剩余的存储空间*/
}
}
|
函数morecore用于向操作系统请求存储空间。在设置完size字段后,morecore函数调用free函数把多余的存储空间插入到空闲区域中。
morecore
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| #define NALLOC 1024
/*morecore函数:向系统申请更多的存储空间*/
static Header *morecore(unsigned nu)
{
char *cp, *sbrk(int);
Header *up;
cp = sbrk(nu*sizeof(Header));
if ( cp == (char *) - 1) /*没有空间*/
return NULL;
up = (Header *)cp;
up->s.size = nu;
free((void*)(up+1));
return freep;
}
|
free函数从freep指向的地址开始,逐个扫描空闲块链表,寻找可以插入空闲块的地方。该位置可能在两个空闲块之间,也可能在链表的末尾。在任何一种情况下,如果被释放的块与另一空闲块相邻,则将这两个块合并起来。合并两个块的操作很简单,只需要设置指针指向正确的位置,并设置正确的块大小就可以了。
free
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| /*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;
}
|
typedef和union的使用解决了地址对齐(假定sbrk返回的是合适的指针)问题