垃圾收集
未能释放已分配的块是一种编程错误。如下代码
void garbage()
{
int *p = (int *)malloc(15213);
return ;
}
程序员忘记释放p,导致p占用本来可以满足后面分配请求的堆空间。 垃圾收集器(garbage collector)是一种动态存储分配器,自动释放不再需要的称为垃圾的块,自动回收堆存储过程叫做垃圾收集,垃圾收集器定期识别垃圾块,并相应的调动free,将这些块放回到空闲链表中。 我们主要讨论标记清除法
基本知识
垃圾收集器将存储器视为一张有向可达图,如下图所示 垃圾收集器的角色是维护可达图的某种表示,并通过释放不可达节点将它们返回给空闲链表,来定期回收它们。 像ML和Java对应用如何创建和使用指针有很严格的控制,能彀维护可达图的一种精确表示,可以回收所有的垃圾,然而C和C++通常步能维持可达图的精确表示,这样的收集器叫做保守的垃圾收集器,每个可达块都被正确的表示为可达,而一些不可达节点错误的被标记位可达。
Mark&Sweep垃圾收集器
Mark&Sweep垃圾收集器由标记(Mark)阶段和清除阶段组成,标记阶段标记出根节点的所有可达和已分配的后继,后面的清除阶段释放每个未被标记的已分配块。典型地,块头部中空闲的低位中的一位来表示这个块是否被标记了。
typedef void* ptr
ptr isPtr(ptr p) //如果p指向一个已分配的字,就返回一个指向这个块的起始位置的指针p,否则返回NULL
int blockMarked(ptr b) //如果已经标记了块b,就返回true
int blockAllocated() //如果块b是已分配的,就返回true
void markBlock(ptr b) //标记块b
int length(ptr b) //返回块b的以字为单位的长度(包括头部)
void unmarkBlock(prt b) //将块b的状态由已标记改为未标记
ptr nextBlock(ptr b) //返回堆中b的后继
标记阶段调用下图所示的Mark函数
void mark(ptr p) {
if((b = isPtr(p)) == NULL)
return;
if (blockMarked(b))
return;
markBlock(b);
len = length(b);
for(int i = 0; i < len; i++)
mark(b[i]);
return;
}
在标记阶段的末尾,任何未标记的已分配块都被认定为不可达的,是垃圾 Sweep函数如下所示
void sweep(prt b, ptr end) {
while (b < end) {
if (blockMarked(b))
unmarkBlock(b);
else if (blockAllocated(b))
free(b);
b = nextBlock(b);
}
return;
}
下图展示了一个小堆的Mark&Sweep图形化解释 C语言GC有以下两点问题
- C不会用任何类型信息来标记存储器位置,对isPtr没有一种明显的方式来判断参数p是否为指针
- 对isPtr没有明显的方式来判断p是否指向一个已分配块的有效载荷中的某个位置
对第二个问题的解决方法是将已分配块集合维护成一颗平衡二叉树,这颗树保持这样一个属性:左子树的所有块都放在较小的地址处,而右子树的所有块都放在较大的地址处。 idPtr(ptr p) 函数用树来执行对已分配块的二分查找。平衡树方法会保证标记所有从根节点可达的节点,单它是保守的。