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;

建立虛擬機的函數:code

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;
}

有了它咱們就能夠把不一樣類型的對象壓到棧裏了:get

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;
}

最終以下:

輸入圖片說明

相關文章
相關標籤/搜索