关键字:数据库设计由于很多垃圾回收算法都是在编译器层面实现的,而编译器层面实现起来牵涉的内容很复杂,一般软件人员理解起来很困难,实际上他们也没有必要去详细了解编译器层面的垃圾回收算法是如何实现的,为了讲清楚垃圾回收的原理,我们还是以一个用户程序层面的垃圾回收算法的具体实现来进行讲解。
前面提到,引用计数算法简单易懂,且可以在用户程序层面实现,下面就以引用计数算法来实现一个C++用户程序层面的垃圾回收算法。
1. C++用户程序层的垃圾回收实现原理
首先要了解清楚C++用户程序层的垃圾回收实现原理,引用计数算法中,如何对一个已分配内存进行计数的增减操作呢?先看一段C++的程序吧。
int *p;
{
int *q = new int;
*q = 100;
p = new int;
*p = 200;
}
在这段程序中,当执行“*q= 100;”语句时,需要对分配的内存引用计数加1,当退出大括号“}”后,由于指针变量q已经被编译器释放掉了,所以这时指向的内存的引用计数要减1。但在上面这段程序中,是没有实现引用计数功能的。要实现引用计数功能,就必须执行int *q = new int;这样的语句能自动给分配的内存增加引用计数,而指针变量q释放时由于是编译器实现的,用户程序并不知道,所以还需要找到发现变量被编译器释放的方法。
2. 模拟指针的C++实现
如果可以设计一个类来模拟指针,那么就可以利用类的构造函数和析构函数来操作指针变量的引用计数,幸运的是C++中的语法提供了这种功能,可以通过重载运算符“*”、“,”、“=”和“->”来实现。
template <class T> class GCPtr {
T *m_pAddr;
public:
GCPtr(T *t=NULL){m_pAddr = t;};
~GCPtr();
T &operator*() { return *m_pAddr;};
T *operator->() { return m_pAddr;};
T *operator=(T *t) { m_pAddr = t;};
GCPtr & operator=(GCPtr &r) { m_pAddr = r.m_pAddr;};
};
在通过如下方式定义一个对象p时:
GCPtr<int> p;
可以将对象p如int类型指针一样来使用,实际的操作都是通过重载运算符来操作GCPtr类里面的pAddr成员实现的。
有了模拟指针,就可以通过类GCPtr来定义任意类型的指针。只要在GCPtr的构造函数和析构函数里进行引用计数的增减,就可以实现引用计数方法的垃圾回收了。
也许有些读者认为可以直接在析构函数里将指针指向的内存释放,但这个想法是在只有这一个指针指向释放的内存的情况下才可行,如果有其他指针指向同一块内存,并且其他指针的生存期更长,就会出现程序运行异常,下面可以看一段简单的程序。
GCPtr<int> p;
p = new int;
{
GCPtr q;
q = p;
}
*p = 100;
以上这段程序中p和q指向的是同一块内存,如果在析构函数中直接将内存释放,那么“*p=100;”这条语句将出现异常,所以不能简单地将一块内存释放掉,必须确认没有指针指向它时才可以释放。因此必须给内存加上引用计数,有多少个指针指向它,计数就为多少,如果引用计数为0就表示没有指针指向它,这时才可以将内存释放掉。
3. 引用计数的实现
上面实现了一个模拟指针的功能,要将模拟指针变成一个具有引用计数功能的模拟指针,首要问题就是要为每块分配的内存找一个保存引用计数的地方。