今天突发奇想地想学习下内存管理(其实是报的腾讯终端开发,怕面试被问到如何实现内存管理模块)。于是找找资料,写了段代码,可以实现基于最佳适应法和循环首次适应法的内存分配。
大家都知道,我们malloc的时候操作系统维护着一张双链表记录堆里面的空闲内存块情况,每个节点对应一块内存。
最佳适应法:分配内存(大小为size)的时候,从表头开始搜索,找那块比size大的最小空闲内存块,进行分配,余下的部分变成一块空闲内存块插入到链表中
循环首次适应法:该算法是首次适应算法的变种。在分配内存空间时,不再每次从表头(链首)开始查找,而是从上次找到空闲区的下一个空闲开始查找,直到找到第一个能满足要求的的空闲区为止,并从中划出一块与请求大小相等的内存空间进行分配,余下的部分变成一块空闲内存块插入到链表中,并成为下一次分配搜索的起始内存块。
它们的代码如下(我们分配内存的单位是1KB):
- //内存块首地址为56000(假定内存空间的低址部分56MB(即0~56M-1)作为系统区和不参与分配过程),总大小为200000
- enum { ADDR = 56000, TOTAL = 200000, MAX = 20000, MIN = 100, CYCLE = 1, BEST = -1, FULL = 1, FREE = 0, N = 2000 };
- struct node
- {
- struct node* prev; //前一个内存块
- struct node* next; //后一个内存块
- int number; //序列号
- int addr; //首地址
- int size; //大小
- int status; //状态:空闲/使用
- };
- typedef struct node block;
- void init ();
- int request ();
- int cycle ( int size );
- int best ( int size );
- /*occupied:占用的内存总量
- count:占用的内存块数目
- compare:比较次数
- algo:算法
- head:表头
- tail:初始时的表尾
- last:循环首次适应算法中下一次搜索的起始内存块
- */
- int occupied = 0, count = 0, compare = 0, algo = CYCLE;
- block *head = NULL, *tail = NULL, *last = NULL;
- //初始化链表
- void init ()
- {
- block *work, * temp;
- occupied = 0;
- count = 0;
- compare = 0;
- //如果链表不为空,清空链表
- if ( head != NULL )
- {
- work = head->next;
- while ( work != head )
- {
- temp = work;
- work = work->next;
- free(temp);
- }
- free(head);
- }
- //初始化链表头尾指针
- head = (block*)malloc(sizeof(block));
- tail = (block*)malloc(sizeof(block));
- last = tail;
- head->prev = tail;
- head->next = tail;
- tail->prev = head;
- tail->next = head;
- head->addr = 0;
- head->size = ADDR;
- head->status = FULL;
- tail->addr = ADDR;
- tail->size = TOTAL;
- tail->status = FREE;
- }
- //请求分配内存
- int request ()
- {
- int (*fp)(int);
- int size = random(MIN,MAX), addr;
- if ( algo == CYCLE )
- {
- fp = cycle;
- }
- else if ( algo == BEST )
- {
- fp = best;
- }
- printf("尝试申请一块大小为%dKB的内存",size);
- if ( (addr = (*fp)(size)) == 0 )
- {
- printf(" 内存空间不足,分配失败\n");
- return 0;
- }
- else
- {
- printf(" 在%d位置成功分配\n",addr);
- return 1;
- }
- }
- //循环首次适应算法
- int cycle ( int size )
- {
- //从上一次空闲内存块(last指向)开始搜索
- block* work = last, *start = last;
- if ( last == NULL )
- return 0;
- //沿着链表方向搜索,找到一块大小满足要求的空闲内存块作为待分配内存块
- while ( work->status == FULL || work->size < size )
- {
- work = work->next;
- ++compare;
- //遍历完整个链表还没找到,返回0
- if ( work == start )
- return 0;
- }
- ++compare;
- //大小超过要分配的内存,当前内存块被分配,多出来的一部分变成空闲内存块插入到被分配的内存块之后,让last指向它
- if ( work->size > size )
- {
- block* remain = (block*)malloc(sizeof(block));
- remain->addr = work->addr + size;
- remain->size = work->size - size;
- remain->status = FREE;
- last = remain;
- work->size = size;
- work->status = FULL;
- remain->prev = work;
- remain->next = work->next;
- work->next->prev = remain;
- work->next = remain;
- }
- else
- {
- //大小刚好,则last指向下一个空闲内存块
- block* temp = work;
- work->status = FULL;
- while ( temp->status == FULL )
- {
- temp = temp->next;
- if ( temp == work )
- break;
- }
- if ( temp == work )
- {
- last = NULL;
- }
- else
- {
- last = temp;
- }
- }
- ++count;
- occupied += size;
- return work->addr;
- }
- //最佳适应算法
- int best ( int size )
- {
- block* work = head->next, *fit;
- //沿着链表头寻找未占用的,大小大于当前申请内存的内存块
- while ( (work->status == FULL || work->size < size) && work != head )
- {
- work = work->next;
- //每次判断,比较次数+1
- ++compare;
- }
- ++compare;
- //无法找到满足要求的内存块,返回0
- if ( work == head )
- return 0;
- //当前内存块大小满足要求
- fit = work;
- //寻找大小满足要求的最小空闲内存块
- while ( work != head )
- {
- work = work->next;
- ++compare;
- if ( work->status == FREE && work->size >= size && work->size < fit->size )
- fit = work;
- }
- //大小超过要分配的内存,当前内存块被分配,多出来的一部分变成空闲内存块插入到被分配的内存块之后
- if ( fit->size > size )
- {
- block* remain = (block*)malloc(sizeof(block));
- remain->addr = fit->addr + size;
- remain->size = fit->size - size;
- remain->status = FREE;
- fit->size = size;
- fit->status = FULL;
- remain->prev = fit;
- remain->next = fit->next;
- fit->next->prev = remain;
- fit->next = remain;
- }
- //大小正好,只改变当前内存块状态
- else
- {
- fit->status = FULL;
- }
- //占用内存块数目+1
- ++count;
- //占用内存块总大小增加
- occupied += size;
- //返回申请到的内存首地址
- return fit->addr;
- }
内存回收:这里用于测试,我们随机回收一块内存,如果内存前后有未占用内存,则合并。
- void recycle ()
- {
- if ( count == 0 )
- {
- printf("无已分配的内存块\n");
- return;
- }
- else
- {
- int n = random(1,count);
- block* work = head->next, *prev, *next;
- do
- {
- if ( work->status == FREE )
- {
- work = work->next;
- continue;
- }
- else
- {
- if ( --n == 0 )
- {
- printf("回收了位于%d的内存块,其大小为%dKB\n",work->addr,work->size);
- prev = work->prev;
- next = work->next;
- occupied -= work->size;
- if ( prev->status == FREE && next->status == FREE )
- {
- prev->size += work->size + next->size;
- prev->next = next->next;
- next->next->prev = prev;
- if ( next == last )
- last = prev;
- free(work);
- free(next);
- }
- else if ( prev->status == FREE && next->status == FULL )
- {
- prev->size += work->size;
- prev->next = next;
- next->prev = prev;
- free(work);
- }
- else if ( prev->status == FULL && next->status == FREE )
- {
- work->size += next->size;
- work->status = FREE;
- work->next = next->next;
- next->next->prev = work;
- if ( next == last )
- last = work;
- free(next);
- }
- else
- {
- work->status = FREE;
- }
- --count;
- return;
- }
- else
- {
- work = work->next;
- }
- }
- }
- while ( n > 0 );
- }
- }
作为一个刚接触内存管理的菜鸟,滋生如下问题,望牛人指教:
1. 我觉得在自己的项目里添加一个内存模块是不是整个项目的内存分配要合理一些(先malloc一块大内存,再对其管理)?
2.内存分配会产生碎片,那对于碎片如何回收呢,如果我规定分配的内存块大小都必须是2k的倍数(2k,4k,8k),对于每个请求的内存分配给它最相近的内存大小(如请求3k,分配4k的内存块给它),产生的内存碎片是不是会少很多?
3.如果我的项目最多只需要分配几个不同大小的内存块(如3个12k,一个43k,2个6k),是不是先malloc(3*12+42+2*6)k内存,再分割成3个12k,一个43k,2个6k的内存块,然后每次要分配的时候去FIT,是不是分配的效率会提升很多?