說明:代碼實現借鑑這邊文章函數
查找及回收無用對象的方法有不少種,最簡單也是最先的一種方法,叫「標記-清除法」。 過程:測試
#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; }
最終以下: