本文共 2512 字,大约阅读时间需要 8 分钟。
在OpenVPN中,一种很不错的内存管理方案是基于链表的,该方案的实现使用了一个gc_arena结构体,该结构体的作用就是将所有的动态分配的内存块收集汇集起来,然后就可以在一个地方统一释放,c语言对动态内存管理的劣势就是无法跟踪动态内存,因此也就将管理动态内存的事情交给了程序员来完成,然后就有了无数次的由于内存泄露导致的bug,虽然c++实现了很多内存管理方案,比如智能指针之类的,但是其复杂性无不揉进了c++这种重量级语言本身,使得本已复杂的语言带上特定的库之后更加臃肿,并且在c++中实现的动态内存管理无非就是使用了构造函数,析构函数之类的,规范相当地复杂,如果有人觉得不复杂,那么他要么是一个初学c++的人,要么是一个学了c但是没有学好的人。 如果将这个话题展开谈,动态内存管理其实并不是很复杂,其唯一的技术难题就是跟踪动态内存的分配,而不是具体的释放时机,一旦所有的动态分配被成功跟踪了,那么释放时机就是程序员的事情了,以前程序员由于跟踪不了动态内存分配而导致内存泄露,那实在不是程序员的错,毕竟程序越来越大越不可控,而每个人也不一定都是高手,但是如果你时刻都能得到当前分配了哪些内存,但是还是泄露,那就是你个人问题了,所以说,只要做到动态内存分配的汇总即可。OpenVPN的内存管理方案就是巧妙地将动态分配的内存组织成了链表,然后在“合适”的时机来统一释放,这个释放不是释放某一个内存块,而是释放被收集的所有的内存块。首先看一下这个机制的基础设施: struct gc_entry { //gc机制的单向链表 struct gc_entry *next; }; struct gc_arena {//最重要的外层结构体,gc机制构建于之上 struct gc_entry *list; }; static inline struct gc_arena gc_new (void) { //分配一个gc,注意是inline,否则返回栈上的缓冲区是错误的 struct gc_arena ret; ret.list = NULL; //初始化 return ret; } static inline void gc_free (struct gc_arena *a) { //链表的释放,释放掉链表上所有的动态内存区域 if (a->list) x_gc_free (a); } void x_gc_free (struct gc_arena *a) { //具体的释放动作 struct gc_entry *e; e = a->list; a->list = NULL; while (e != NULL) { //遍历链表的元素,逐个释放之 struct gc_entry *next = e->next; free (e); e = next; } } void *gc_malloc (size_t size, bool clear, struct gc_arena *a) { //重要的分配函数 void *ret; if (a) { struct gc_entry *e; //注意下面分配的空间大小要加上gc_entry的大小,gc_entry负责管理 e = (struct gc_entry *) malloc (size + sizeof (struct gc_entry)); check_malloc_return (e); ret = (char *) e + sizeof (struct gc_entry); //只把除去管理数据的内存返回给调用者 e->next = a->list; //将该块内存链接入链表 a->list = e; } ... if (clear) //分配一块清0内存 memset (ret, 0, size); return ret; } 以上代码很清晰,下面则是使用上述机制的一个例子: int mmalloc(struct gc_arena *gc) {//如果下面的换成malloc(1000)的话,则瞬间就完蛋了。 char *buf = (char *)gc_malloc (1000, 1, gc); } int main(int argc, char **argv) { struct gc_arena gc = gc_new(); gc.list = NULL; int i = 0, j = 0; while (1) { i++; while (j++ < 10) { mmalloc(&gc); } j = 0; gc_free(&gc); } } 何谓“合适”的时机,到底在什么时候释放呢?这实际上是个问题,答案就是需要你自己掌握,也就是说你必须知道什么时候那些分配的缓冲区没有用了,这个gc机制所做的仅仅是帮你搜集到所有的缓冲区,而不是负责帮你决定释放的时机,如果你理解linux kernel的计数器机制的话,那么一定会为这种内存管理机制叹为观止,不过说实话,即使是计数器机制也是需要程序员自己把握什么时候调用free,而在free中递减计数器,计数器机制为你做的只是控制计数器为当前正在使用该内存的路径数量,具体何时该释放还是需要程序员自己控制,实际上不要贪图任何毫无代价的自动内存管理机制,也没有那样的机制,目前实现的需要付出代价的内存管理机制主要有两种,一种是c++牺牲了简单性原则实现的利用诸如构造函数/析构函数之类的机制,它实质上是利用一些c++的固定标准来实现的,另一种则是牺牲了性能和可控性的垃圾收集机制,java使用该种方式,java虚拟机需要维护一个对象引用图,定期触发垃圾收集而将游离的图节点内存回收。可见唯一不需要付出系统代价的就是c的实现了,可是却需要程序员在写代码时付出代价,不管怎样,就简单就是美的原则来讲我宁可使用C的方法也不喜欢c++或者java那种看似简单实则臃肿的方式
本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1271830
转载地址:http://folum.baihongyu.com/