C语言如何获取所有堆内存分配记录以实现mark-sweep垃圾回收器
C语言自研标记清除GC的堆分配记录实现方案
首选方案:封装标准内存分配接口(教学场景最优)
教学场景下完全不需要考虑复杂的系统级堆结构读取,封装分配接口的方案足够满足演示需求,实现成本极低
你可以自定义一套内存分配/释放函数,替换项目内所有标准库malloc/calloc/realloc/free调用,所有分配的堆块信息会自动存入你维护的全局链表中,示例实现如下:
// 堆分配记录节点,内置标记位可直接用于后续标记阶段 typedef struct gc_alloc_node { void* ptr; // 分配的堆内存起始地址 size_t size; // 堆块大小 unsigned char marked; // 标记清除用的标记位 struct gc_alloc_node* next; } gc_alloc_node; // 全局的堆分配记录链表头 static gc_alloc_node* gc_alloc_list = NULL; // 封装后的malloc函数 void* gc_malloc(size_t size) { void* raw_ptr = malloc(size); if (!raw_ptr) return NULL; // 新建分配记录节点插入链表头部 gc_alloc_node* node = malloc(sizeof(gc_alloc_node)); node->ptr = raw_ptr; node->size = size; node->marked = 0; node->next = gc_alloc_list; gc_alloc_list = node; return raw_ptr; } // 封装后的free函数 void gc_free(void* ptr) { gc_alloc_node** prev = &gc_alloc_list; gc_alloc_node* curr = gc_alloc_list; while (curr) { if (curr->ptr == ptr) { *prev = curr->next; free(curr->ptr); free(curr); return; } prev = &curr->next; curr = curr->next; } // 未匹配到分配记录则判定为非法释放,教学场景直接报错终止即可 fprintf(stderr, "gc_free: invalid pointer %p\n", ptr); abort(); }
注意事项:
- 整个项目所有堆内存操作必须调用你封装的
gc_前缀函数,不能直接调用标准库分配接口,否则会遗漏分配记录 - 后续标记清除逻辑可以直接遍历
gc_alloc_list链表,不需要再额外获取堆分配信息
备选方案:Hook系统分配接口(适配已有代码)
如果你的GC需要适配已完成的C代码,不想修改所有内存分配调用,可以通过Hook方式拦截标准库的内存分配调用:
- Linux平台可通过
LD_PRELOAD加载自定义动态库,替换glibc的malloc/free等函数实现 - Windows平台可通过IAT Hook、Inline Hook拦截系统内存分配函数
- 该方案兼容性更强但实现复杂度更高,没有旧代码适配需求的教学项目不建议选择
标记清除逻辑对接说明
拿到完整的分配记录链表后,可按标准流程实现标记清除:
- 标记阶段:遍历所有GC根(栈变量、全局变量、静态变量存储的指针),如果指针指向分配记录内的堆块,就将对应节点的
marked位置为1 - 清除阶段:遍历整个
gc_alloc_list链表,释放所有marked位为0的堆块和对应节点,同时将剩余节点的marked位重置为0,等待下一轮GC触发
内容的提问来源于stack exchange,提问作者procoder35
相关产品推荐
相关产品推荐

