最近在看Lua的源码,但Lua的代码太庞大了(虽然相较于其他语言非常精巧),所以想自己写一个小的带垃圾回收的虚拟机,加深自己对垃圾回收原理的理解和记忆,以下是代码:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define STACK_MAX 256 // 最大的栈数量
#define INIT_GC_VAL_MAX_NUM 8 // 初始最大的GC Value数量,达到后触发第一次GC
// 虚拟机中一共有两种类型,整形和元组
typedef enum {
VALUE_TYPE_INT,
VALUE_TYPE_PAIR
} ValueType;
// 值定义
typedef struct _Value {
ValueType type; // 值类型
uint8_t marked; // GC 标记
struct _Value* next; // GC 对象链
// 值集合
union {
int value;
struct { // 有两个元素的元组
struct _Value* v1;
struct _Value* v2;
};
};
} Value;
// 虚拟机定义,模仿Lua叫State
typedef struct {
Value* stack[STACK_MAX]; // 栈
int stackSize; // 当前栈大小
Value* gcFirstVal; // 指向对象链表的首个节点
int gcValNum; // 当前对象数量
int gcMaxValNum; // 最大对象数量,达到后触发GC
} State;
void gc(State* s);
// 创建一个新的虚拟机
State* newState() {
State* s = malloc(sizeof(State));
s->stackSize = 0;
s->gcFirstVal = NULL;
s->gcValNum = 0;
s->gcMaxValNum = INIT_GC_VAL_MAX_NUM;
return s;
}
// 向栈中压入数据
void push(State* s, Value* v) {
s->stack[s->stackSize++] = v;
}
// 从栈中弹出数据
Value* pop(State* s) {
return s->stack[--s->stackSize];
}
// 创建一个新的值
Value* newValue(State* s, ValueType type) {
if (s->gcValNum == s->gcMaxValNum) {
gc(s);
}
Value* v = malloc(sizeof(Value));
v->type = type;
v->next = s->gcFirstVal;
s->gcFirstVal = v;
v->marked = 0;
s->gcValNum++;
return v;
}
// 向栈中压入一个整形数值
void pushInt(State* s, int value) {
Value* v = newValue(s, VALUE_TYPE_INT);
v->value = value;
push(s, v);
}
// 依据栈顶的两个值创建一个元组
Value* pushPair(State* s) {
Value* v = newValue(s, VALUE_TYPE_PAIR);
v->v1 = pop(s);
v->v2 = pop(s);
push(s, v);
return v;
}
// 以下是垃圾回收实现
// 标记单个对象
void mark(Value* v) {
if (v->marked) return;
v->marked = 1;
if (v->type == VALUE_TYPE_PAIR) {
mark(v->v1);
mark(v->v2);
}
}
// 标记所有对象
void markAll(State* s) {
for (int i = 0; i < s->stackSize; i++) {
mark(s->stack[i]);
}
}
// 清除未标记的对象
void sweep(State* s) {
Value** v = &(s->gcFirstVal);
while (*v) {
if (!(*v)->marked) {
Value* unreached = *v;
*v = unreached->next;
free(unreached);
s->gcValNum--;
} else {
(*v)->marked = 0;
v = &(*v)->next;
}
}
}
// 垃圾回收
void gc(State* s) {
int valNum = s->gcValNum;
markAll(s);
sweep(s);
s->gcMaxValNum = s->gcValNum == 0 ? INIT_GC_VAL_MAX_NUM : s->gcValNum * 2;
printf("Collected Values count: %d, current Value count: %d\n", valNum - s->gcValNum, s->gcValNum);
}
// 释放虚拟机
void freeState(State* s) {
s->stackSize = 0;
gc(s);
free(s);
}
void main() {
State* s = newState(); //
pushInt(s, 1); // 1
pushInt(s, 2); // 1, 2
pop(s); // 1
pop(s); //
pushInt(s, 3); // 3
pushInt(s, 4); // 4
pushPair(s); // (3, 4)
pushInt(s, 1); // (3, 4), 1
pushInt(s, 2); // (3, 4), 1, 2
Value* v1 = pushPair(s); // // (3, 4), (1, 2)
pushInt(s, 3); // 这里会触发GC // (3, 4), (1, 2), 3
pushInt(s, 4); // (3, 4), (1, 2), 3, 4
Value* v2 = pushPair(s); // (3, 4), (1, 2), (3, 4)
v1->v2 = v2;
v2->v1 = v1;
pop(s); // (3, 4), (1, 2) // 这样写可能不太准确
pop(s); // (3, 4)
freeState(s);
}
运行后输出:
Collected Values count: 2, current Value count: 6
Collected Values count: 9, current Value count: 0
这只是个人用来学习的,所以没有加什么防御编程之类的东西,仅仅是展示标记清除的核心算法。