我们平时在linux或者其他的嵌入式系统中经常会用到两个函数,malloc和free。因为有这种动态内存分配的方法,所以给我们日常的编程带来了很多的便捷之处。那么,这两个函数其工作机理是怎么样的呢?在这里,我不想太过复杂地去阐述操作系统中其真正的运行过程,只是把这种动态内存分配的一些思路给说明一下,因为真正的动态内存分配是十分复杂的,我这里只能简单地描述其中的一种,并用C来把这种动态内存分配的方法给模拟出来。
malloc就是从一块空间中开辟出有一部分能用的内存块,返回给用户的是这块分配好的空间的首地址。而free正好相反,其作用是把分配好的空间重新归还于原来的地方,就是我们常说的“释放内存”。
相对free而言,malloc还是比较好理解,而free中的释放又是怎么一回事呢?简单的来说,比如我们在一块内存中占用了其间的一块空间,那么此时操作系统中的某个机制就会把这个事件记录下来,给已经分配给用户的这块空间作好标志,防止下次再次分配的时候会涉及到这部分空间,造成指针越界。
而free的实现和其工作机理,要比malloc要复杂的多了。其要做的工作,不仅仅是把分配过的空间的标志位给擦除掉,而且还要把内存中的碎片进行整合,这是非常必要的。内存中的碎片存在的越多,本来可分配的内存会变得越来越少,最后变得不可分割,从而导致malloc失败,导致程序异常中断。
可以用C来对上述的动态内存分配进行模拟实现。思路是,取一块数组,一部分可分配空间,一部分是用于记录当前分配空间状态的目录,通过查询目录来获取此时的内存分配情况,管理可分配空间,从而实现动态分配方法。实现代码如下:
#include<stdio.h>
#include<time.h>
#include<string.h>
#define MEMSIZE 10000 //总内存块的大小
#define CATALOG_COUNT_MAX 100 //目录的个数
char memory[MEMSIZE];
typedef struct mumm
{
void *start; /*指向当前分配空间的首地址*/
int used; /*分配的标志位,1表示已分配,0表示空闲*/
int size; /*该目录指向的空间的大小*/
}catalog;
catalog *memu; //目录指针
void init()
{
/* 定义好一串目录链表,用于查询分配,个数为100个。同时给目录进行初始化 ,其中,第一个目录指向的就是未分配的总内存大小*/
memu = (catalog *)(memory + MEMSIZE - sizeof(catalog)*CATALOG_COUNT_MAX);
memu[0].start = memory;
memu[0].used = 0;
memu[0].size = MEMSIZE - sizeof(catalog)*CATALOG_COUNT_MAX;
int i;
for(i = 1;i < CATALOG_COUNT_MAX; i++)
{
memu[i].start = NULL;
memu[i].used = 0;
memu[i].size = 0;
}
}
/* malloc 的实现方法 */
void *mymalloc(int size)
{
int i,j;
/*查询所有目录,看是否有空闲的数据块*/
for(i = 0;i < CATALOG_COUNT_MAX; i++)
{
/*找到了一块数据块*/
if(memu[i].size >= size && memu[i].used == 0)
{
/*标记为使用中*/
memu[i].used = 1;
for(j = 0;j < CATALOG_COUNT_MAX; j++)
{
/*查询目录,寻找未有分配状态的目录*/
if(memu[j].used == 0 && memu[j].size == 0)
{
/*若找到,把该目录指针置于已分配的空间的后面,并分配余下空间大小,并返回*/
memu[j].start = memu[i].start + size;
memu[j].size = memu[i].size - size;
memu[i].size = size;
return memu[i].start;
}
}
break;
}
}
return memu[i].start;
}
/*free 的实现方法*/
void *myfree(void *p)
{
int i, j, k;
for(i = 0;i < CATALOG_COUNT_MAX; i++)
{
/*从目录中寻找要释放的内存,通过其指向的首地址和使用状态来寻找*/
if(p == memu[i].start && memu[i].used == 1)
{
/*找到了,把标志位清0。但此时工作并未结束,需要整理内存碎片*/
memu[i].used = 0;
for(j = 0; j < CATALOG_COUNT_MAX; j++)
{
/*寻找目录,找到指向要释放的空间的后一块内存,如果该内存仍在使用中,则放弃合并,如果未使用,此时把后一块空间合并到要释放空间的内存中来,组成一块新的大的内存块*/
if((memu[i].start + memu[i].size == memu[j].start) && memu[j].used == 0)
{
memu[j].start = NULL;
memu[i].size = memu[j].size + memu[i].size;
memu[j].size = 0;
break;
}
}
for(k = 0;k < CATALOG_COUNT_MAX;k++)
{
/*同上,寻找目录,找到指向要释放的空间的前一块内存,如果该内存仍在使用中,则放弃合并,如果未使用,此时把要释放的空间合并到前一块空间的内存中来,组成一块新的大的内存块*/
if((memu[k].start + memu[k].size == memu[i].start) && memu[k].used == 0 )
{
memu[i].start = NULL;
memu[k].size = memu[i].size + memu[k].size;
memu[i].size = 0;
break;
}
}
break;
}
}
}
/* 该主函数为压力测试,如果够完成9999次malloc和free,那么表示该动态分配方法没有问题 */
int main(int argc, char *argv[])
{
init();
int i;
char *pp[80];
int n;
char ch;
for(i = 0;i < 80;i++)
pp[i] = "-1";
srand(time(NULL));
for(i = 0;i < 10000;i++)
{
printf("i = %d\n",i);
n = random()%80;
if(strcmp(pp[n], "-1") == 0)
{
pp[n] = (char *)mymalloc(random()%100 + 1);
if(pp[n] == NULL)
break;
strcpy(pp[n], "a");
printf("malloc %d\t%x\n", n, pp[n]);
}
else
{
printf("free %d\t%x\n", n, pp[n]);
myfree(pp[n]);
pp[n] = "-1";
}
}/*
int *p = mymalloc(50);
int *a = mymalloc(100);
int *b = mymalloc(100);
int *c = mymalloc(100);
int *d = mymalloc(1000);
int *f = mymalloc(7450);
printf("%p\n",p);
printf("%p\n",a);
printf("%p\n",b);
printf("%p\n",c);
printf("%p\n",d);
myfree(a);
myfree(b);
int *e = mymalloc(150);
printf("%p\n",e);
int *g = mymalloc(100);
printf("%p\n",g);
int *h = mymalloc(100);
printf("%p\n",h);
int *i = mymalloc(100);
printf("%p\n",i);
*/
return 0;
}
其结果如下:
i表示malloc和free的次数,如malloc 60 8049339 表示malloc 60byte大小空间地址为0x8049339。