今天我们看一下lcc的存储管理,在我们写的一些复杂的程序中,都需要用到动态内存的分配,即使用malloc,但我们都知道使用malloc就需要显式的使用free来释放掉。问题是有很多程序员会忘记释放空间,造成内存泄漏。
lcc在管理内存的思想是统一释放,这主要是来自于我们常遇到的窗口,就是在你关闭窗口时,所分配的按钮,滚动条等都释放了。
lcc主要是使用了分配区,释放空间的大小也是这个分配区,所以生命周期相似的对象就会统一释放掉。而每一个分配区由一组内存块组成链表,每一个内存块都有一个结构体来指定。
首先是这个头部结构体的表示:
struct block
{
struct block *next;
char *limit;
char *avail;
};
limit是指定内存块内地址的最大值,avail是指定所能分配的地址的最小值,也就是在数据块中大于avail的地址都已经分配出去了。所能用的地址大小是limit - avail.
在这里我们用到一个数据结构是:
union align
{
long l;
char *p;
double d;
void (*f)(void);
};
这个共用体的主要作用是使所分配的数据在地址空间上是对齐的,联合里给出了宿主机的最小对齐字节数。
同理:
union header
{
struct block b;
union align a;
};
我想不用说你大概也知道它的作用来吧,对,就是为了ap->avail指向的地址是对齐的。
下面是数据结构的初始化:
struct block
first[] = { {NULL}, {NULL}, {NULL} };
*arena = { &first[0], &first[1], &first[2] };
下面主要是分配空间的细节:
ap = arena[a];
首先得到要分配的分配区的指针,其实这个指针是指向分配区中能分配的内存块,其他的是已经被分配完的。
n = roundup(n , sizeof(union align));
roundup宏返回的是离n最近的sizeof(union align)倍数的值,其实它就是为了字节对齐。
if (ap->avail + n > ap->limit)
{
if((ap->next = freeblocks) != NULL)
{
freeblocks = freeblocks->next;
ap = ap->next;
}
// 我想你应该知道freeblocks指针的作用吧,恩,它就是指向那些已经释放来空间的头指针
else
{
unsinged m = sizeof(union header) + n + 10 * 1024;
ap->next = malloc(m);
ap = ap->next;
if(ap == NULL)
{
exit(1);
}
ap->limit = (char *)ap + m;
}
ap->avail = (char *)((union head *)ap + 1);
arena[a] = ap;
}
ap->avail += n;
return ap->avail - n;
这里有一个技巧是:
假设分配形式是: struct T *p;
p = allocate(sizeof *p, a);
我们并没有使用 p = allocate(sizeof T, a);
因为后者只有在p确实是struct T指针时才是正确的,否则就会产生错误,而前者则不存在这样的问题.