之所以会写这个程序是因为培训中有一课是 TDD ( test-driven devp ) , 先写测试后写程序,是一种比较神奇的编程方法,看似不可能,其实这样程序出错的可能性更小。如果大家对 TDD 有兴趣可以去看看其他的书,我就不讨论了。接下来要谈的 MemPool 是课中实践题目,但是由于系统是 linux ,搞的我晕头转向的(命令行操作路径切换),程序没怎么写, linux 的操作到时学会了一些,呵呵。
MemPool 是自己实现的一个内存管理的方法,主要的想法就是每次用户申请内存我们先分配给他,然后当用户释放时我们不调用 free ,而是把申请的内容保持起来,下次再申请直接把这个空间给用户。题目要求分配内存的大小必须是 8byte 对齐( 8 , 16 , 32… ),然后维护一个表格,实现 MemPool_Alloc ( uint size )和 MemPool_Free ( void*p )两个方法:
Size |
Total |
Free |
Point |
8 |
1 |
0 |
NIL |
16 |
1 |
1 |
XXXXXX |
32 |
0 |
0 |
NIL |
Total 是已经申请的内存块数, Free 是当前空闲的快数, Point 指向一个空闲内存的单项链表, NIL 表示空。
题目咋看起来比较简单,只要每次用户调用 MemPool_Alloc 时检查下是否有空闲块,有就返回单链表的第一个元素,没有就申请一块内存;当调用 MemPool_Free 时,把这块内存链到单链表中就 ok 了。这里为了简单起见,把表格定死,假设只有 8 , 16 , 32 ,这 3 个大小,不定死的话就是把这个表格变为一个单项链表。
先说我自己最初的想法:定义 2 个结构体,一个表示表格的每一行,另一个表示每个内存块。刚开始写的很顺,后来写到 MemPool_Free 时问题就出来了。因为只传进来一个参数 void *p ,而我们要归还的话必须得知道这块内存的大小,那怎么知道那?这得取决于我们 MemPool_Alloc 是怎么实现的了,最后我实现的方法是每次新申请内存后就把这块内存链起来,再增加一个 bool bIsFree 选项。这样每次归还时我只要查找到这个 void*p 属于哪个 Memory, 然后就找到了属于哪个 Block ,再设置 bIsFree 为 true 就 ok 了。分配时查找哪个 Memory 的 bIsFree 为 true ,否则申请一个。虽然这样是可以的,但是总要全部查找一遍总觉得效率不高。后来我去问了公司里的一个同事,他是培训中写完程序。发现他的方法很好。
typedef struct tagMemory
{
bool bIsFree;
void *pMem;// 这个是要我们返回给用户的
struct tagMemory *pNext;
}Memory;
typedef struct tagBlock
{
//block size
int nTotal;
int nFree;
Memory *pFreeHead;
}Block;
同事的方法:先把结构体改一下, Memory 的 bool bIsFree 去掉就可以了。因为我唯一遇到的问题就是不知道归还的内存的大小,那我们想办法知道就可以了。或许你也会想到调用系统 free 时不是也没指定它的大小么,是的,因为系统分配时纪录了该块内存的大小。现在我们的方法也是一样的,就是申请空间时多申请一个 int 类型大小的空间来保存空间的大小(其实也可以是其他类型够大就可以了), Alloc 时的关键代码如下:
Size |
Mem |
Void *p = malloc(sizeof int + size);
int *pInt = (int*)p;
*pInt = size;
p = (void*)(pInt+1);
return p;
这里有些对 void* 指针操作的注意事项,
1. 不能直接对 void* 类型的指针进行解引用(就是取里面的内容),因为编译器不知道 void* 指针指向类型的大小。
2. 不能对 void* 指针用 sizeof
3. void* 指针和其他类型的指针可以互相转换(也不是绝对)
最终 Free 时的关键代码如下:
Free((void*)((int*)p-1));
其实这个类似 MemPool 的东西在《 c 语言接口与编程》里也有,只不过他是通过 hash 来查找的,大家有兴趣可以去看看。这段时间以后一直都在学习语言或者是没有接触过的技术,对于这种需要技巧(对我而言)的东西很久没写了,感觉不错,趁热就把它纪录下来。