malloc函数的原理很简单,首先利用系统调用(linux平台和windows平台都提供了从堆区动态分配内存的系统调用)从堆区申请一块内存,每当应用程序申请内存时,从这块内存区截取一块提供给程序。这样可以减少利用系统调用申请内存的次数,提高了效率。就好比批发商从厂商那里批发了很多商品,然后卖给消费者。从而方便了消费者。不用需要商品时都要从厂商那里购买。但是malloc的实现涉及到了很多细节。其中涉及到地址对齐,内存碎片,等等很多问题。要想实现一个malloc函数绝不是一件容易的事情。
在《c语言程序设计》(c语言之父写的那本)一书中,作者给出了一个malloc的模拟实现。一段很小的程序,可以说麻雀虽小五脏俱全。这个程序涉及到了malloc中要考虑的大部分问题,而且实现的非常简洁。我测试了一下和malloc具有一样的功能。代码不太好懂,我在不太容易理解的地方加了一些注释。以下是代码,在linux平台测试通过。如果想仔细了解,请看原书。
#define NALLOC 1024
#define NULL 0
typedef long Align;
union header {
struct {
union header *ptr;//指向下一块空闲快
unsigned size;//当前空闲块的大小
} s;
Align x;//x主要是保证分配的内存为Align的倍数。涉及到内存对齐
};
typedef union header Header;
static Header base;
static Header *freep=NULL;
static Header *morecore(unsigned);
void *mymalloc(unsigned nbytes)
{
Header *p,*prevp;
unsigned nunites;
nunites =(nbytes+sizeof(Header)-1)/sizeof(Header)+1;//分配的内存大小
if((prevp =freep)==NULL){//no free list yet,the first call malloc
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>=nunites){ //big enough
if(p->s.size==nunites)//exactly
prevp->s.ptr=p->s.ptr;
else{//allocate tail end总是从每块空闲快的尾部截取
p->s.size-=nunites;
p+=p->s.size;
p->s.size=nunites;
}
freep=prevp;
return (void *)(p+1);
}
if(p==freep){
if((p=morecore(nunites))==NULL)
return NULL;
}
}
}
void myfree(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;
}
static Header *morecore(unsigned nu)
{
char *cp, *sbrk(int);//linux系统调用,扩展堆空间
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;
myfree((void *)(up+1));
return freep;
}
疑惑:
malloc申请得到的内存在未使用时,分配物理空间了吗?就是虚拟地址映射到物理地址了吗?还是访问这块内存发现缺页之后在分配物理空间呢。