c++ 模拟gc垃圾回收

说明:代码实现借鉴这边文章

查找及回收无用对象的方法有很多种,最简单也是最早的一种方法,叫“标记-清除法”。 过程:

  1. 从根对象开始,遍历整个对象图。每访问一个对象,就把一个标记位设成true。
  2. 一旦完成遍历,找出所有没有被标记过的对象,并清除掉它们。 假设我们正在写一门小语言的解释器。它是动态类型的,有两种对象:int以及pair。下面是一个定义对象类型的枚举:
#include <assert.h>
enum Obj_Type
{
	OBJ_INT,
	OBJ_PAIR
};

实现对象类型:

struct _Object
{
	Obj_Type type;
	unsigned char marked;//标记标志,如果被标记,说明可达。
	//union 
	//{
		int value;//存储的值
		struct  
		{
			_Object* pHead;
			_Object* pTail;
		};
	//};
	_Object *pNext;
};

现在我们可以把它们封装到一个小型虚拟机的结构里了。这个虚拟机在这的作用就是持有一个栈,用来存储当前使用的变量。很多语言的虚拟机都要么是基于栈的(比如JVM和CLR),要么是基于寄存器的(比如Lua)。不管是哪种结构,实际上它们都得有一个栈。它用来存储本地变量以及表达式中可能会用到的中间变量。

#define  MAX_SIZE 256 //堆栈最大数目
#define  INITIAL_GC_THRESHOLD 128 //触发gc的对象数量
typedef struct _tagVM
{
	_Object* objs[MAX_SIZE];
	int max_objs;//产生的最大对象的数目,超过调用gc
	int num_objs;//记录现在的对象数目
//对虚拟机层面,所有的对象应该都是可见的,从用户角度,有些对象可能没有任何引用指向它,对象也就
//不可见,所以
//用链表记录通过虚拟机产生的所有对象。
	_Object *pFirstObj;//链表的头指针
	_Object *pLastObj;//链表的尾指针
	int objs_size;//堆栈大小或者堆栈指针
}VM;

创建虚拟机的函数:

VM* new_VM()
{
	VM *pVm=(VM *)malloc(sizeof(VM));
	pVm->objs_size=0;
	_Object *pObj=create_obj(OBJ_INT);
	pObj->value=0;
	pObj->marked=1;
	pObj->pHead=NULL;
	pObj->pTail=NULL;
	pObj->pNext=NULL;
	pVm->pFirstObj=pObj;//NULL;第一个节点不使用,
	pVm->pLastObj=pVm->pFirstObj;
	pVm->max_objs=INITIAL_GC_THRESHOLD;
	pVm->num_objs=0;
	return pVm;
}

释放虚拟机函数:

void delete_VM(VM *pVM)
{
	_Object *pObj=pVM->pFirstObj;
	while (pObj!=NULL)
	{
		printf("%i\t",pObj->value);
		_Object *tmp=pObj->pNext;
		delete_Obj(pObj);
		pObj=tmp;
	}
	free(pVM);
	pVM=NULL;
}

有了虚拟机后,我们需要对它的栈进行操作:

void push(VM* pVM,_Object *pObj)
{
	assert(pVM->objs_size<=MAX_SIZE ,"Stack overflow!");
	pVM->num_objs++;
	pVM->pLastObj->pNext=pObj;
	pVM->pLastObj=pObj;
	pVM->objs[pVM->objs_size++]=pObj;
	if (pVM->num_objs>pVM->max_objs)
	{
		gcVM(pVM);
	}
}
_Object * pop(VM*pVM)
{
	assert(pVM->objs_size>0 ,"Stack underflow!");
	return pVM->objs[--pVM->objs_size];
}

产生对象的函数:

_Object * create_obj(Obj_Type type)
{
	_Object *pObj=(_Object *)malloc(sizeof(_Object));
	pObj->type=type;
	pObj->marked=0;
	pObj->pNext=NULL;
	return pObj;
}

_Object * new_obj(Obj_Type type)
{
	_Object *pObj=create_obj(type);
	return pObj;
}

有了它我们就可以把不同类型的对象压到栈里了:

void push_int(VM* pVM,int value)
{
	_Object *pObj=new_obj(pVM,OBJ_INT);
	pObj->value=value;
	push(pVM,pObj);
}

_Object * push_pair(VM* pVM,int value)
{
	_Object *pObj=new_obj(pVM,OBJ_PAIR);
	pObj->value=value;
	pObj->pTail=pop(pVM);
	pObj->pHead=pop(pVM);
	push(pVM,pObj);
	return pObj;
}

如果我们有个解析器和解释器来调用这些函数,就是一门完整的语言了。下面是标记-清除过程: 第一个阶段是标记阶段,

void mark(_Object *pObj,unsigned char marked)
{
	if (pObj->marked) return;//防止pair类型相互引用,造成环回,形成无穷递归。
	printf("%i\t",pObj->value);//调试输出
	pObj->marked=marked;
	if (pObj->type==OBJ_PAIR)//pair类型,进行递归
	{
		mark(pObj->pHead,marked);
		mark(pObj->pTail,marked);
	}
}

void mark_all(VM*pVM)
{
	for (int i=0;i<pVM->objs_size;++i)
	{
		//printf("%i\t",pVM->objs[i]->value);
		if (pVM->objs[i]->type==OBJ_PAIR)//堆栈中的非Pair节点,认为不可达
		{
			mark(pVM->objs[i],1);
		}

	}
}

下一个阶段就是遍历所有分配的对象,释放掉那些没有被标记的了,

void sweep(VM* pVM)
{
	_Object *pObj=NULL,*pFrontObj=NULL;
	pObj=pFrontObj=pVM->pFirstObj;
	while (pObj!=NULL)
	{

		if (!pObj->marked)
		{
			printf("%d\t",pObj->value);//调试输出
			pFrontObj->pNext=pObj->pNext;//删除链表节点,调整前后指针指向
			_Object *tmp=pObj;
			pFrontObj=pObj=pFrontObj->pNext;
			free(tmp);
			pVM->num_objs--;//目前对象数目减一
		}
		else
		{
			pFrontObj=pObj;
			pObj=pObj->pNext;
		}

	}
}

最后,有了一个垃圾回收器:

void gcVM(VM*pVM)
{
	mark_all(pVM);
	sweep(pVM);
	//每次回收之后,我们会根据存活对象的数量,更新maxOjbects的值。
	//这里乘以2是为了让我们的堆能随着存活对象数量的增长而增长。
	//	同样的,如果大量对象被回收之后,堆也会随着缩小
	pVM->max_objs = pVM->num_objs * 2;
}

然后,我们在new_obj根据对象产生的数目来触发gc。 我们来产生一些对象来测试gc:

int _tmain(int argc, _TCHAR* argv[])
{
	VM *pVM=new_VM();
	for (int i=0;i<10;++i)//产生一些对象
	{
		push_int(pVM,10);
		push_int(pVM,15);
		push_pair(pVM,20);//使堆栈中丢失前面的两个对象指针
		push_int(pVM,30);//不可达
	}
	printf("mark_all:\n");
	mark_all(pVM);
	printf("sweep:\n");
	sweep(pVM);
	printf("delete_VM:\n");
	delete_VM(pVM);
	getchar();
	return 0;
}

最终如下:

输入图片说明

转载于:https://my.oschina.net/u/221120/blog/895663

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值