一、概述

1、malloc简介

函数所在头文件:<stdlib.h>

函数原型是:void *malloc (size_t n)

函数功能:在内存的动态存储区中分配一个长度为size的连续空间。其参数是一个无符号整形数,返回值是一个指向所分配的连续存储域的起始地址的指针。

2、malloc函数使用注意事项

  • 申请了内存空间后,必须检查是否分配成功。
  • 当不需要再使用申请的内存时,记得释放;释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它。
  • malloc和free函数应该配对使用。如果申请后不释放,就是内存泄露;如果无故释放那就是什么也没有做。释放只能一次,如果释放两次及两次以上会出现错误。
  • 越界使用动态分配的存储块,尤其是越界赋值,可能引起非常严重的后果,通常会破坏程序的运行系统,可能造成本程序或者整个计算机系统垮台。

3、本文的目的

  一直很想亲手实现一个malloc函数,不仅仅是锻炼自己的编程能力,对指针的驾驭能力,而且也是为了解除自己对malloc函数的疑惑--为什么使用malloc函数有上述的注意事项?要想知道原因,只有自己查阅源码,或者自己编写功能类似的源码,而笔者选用了后者。

二、本程序实现机制

 1、块控制头部

  块控制头部结构体定义如下所示:

typedef struct{
unsigned char is_available; /* whether blcok is avaiable */
unsigned int prior_blocksize; /* size of prior block */
unsigned int current_blocksize; /* block size */
}mem_control_block;

  每申请一个内存空间,无论大小(char、short、long)都将其命名为"Block"。在分配一个Block的时候,在堆区(heap)首先存放的是Block的控制块,然后才是有效的数据区。可以想象,假设我们申请一个char型大小的空间,实际上在堆区占据了“sizeof(char)+sizeof(mem_control_block)”。就是通过Block的头部控制块将堆区连成一个链表,通过这个链表可以遍历整个堆区的每一个块。

  is_avilable标记内存块是否被分配,是否可用。prior_blocksize保存了前一个块的大小,current_blocksize保存了当前块的实际有效数据空间大小。有了current_blocksize,我们可以从前向后遍历整个堆区;而有了prior_blocksize,我们可以从后向前遍历整个堆区。也就是说,这个控制块头部使我们为堆区建立了一个双向链表,以便于管理。

2、malloc

  调用malloc函数时,它沿连接表从前向后,寻找一个没有被占用的,而且大到足以满足用户请求所需要的内存块。在寻找过程中,分三种情况。

  第一种情况,倘若可用而且大小刚好合适,那么直接将此块返回就完成了分配。

  第二种情况,倘若可用而且超过了所需要的内存块并且还能容纳一个新块的控制头部,这个时候就将此块一分为二。将分配给用户的那块内存的is_available置0(不再可用),然后将此块的有效数据区首地址作为函数值返回。而新块的控制头部要进行赋值(只有含有头部的块才能称的上一个块),没有控制头部的块不能进行管理。

 第三种情况,倘若可用内存块的空间小于需要的空间,或者虽然超过了但是不能腾出一个控制头部,最终的处理都是跳过改块,移动到链表的下一块进行新一轮的比较。

 3、free

  free函数用于内存块的回收,当不用的内存块调用此函数释放空间的时候,将is_available置1(可以重新分配)。接下来要做的就是整合内存碎片。由于可能多次malloc函数,产生了很多的内存碎片,在释放一个块的时候,不仅要标记此块为“可用”。另外,还需要试着将此块前后的块进行合并,这样才能为将来申请更大的空间做好准备。

4、malloc_init

  初始化函数,主要是确定堆区的起始位置、大小和结束地址,也就是确定堆区的边界。我们申请的内存块必须在堆区内,不得超出边界,否则会修改其他的数据。

  另外,还需要对堆区进行初始化,建立第一个内存块(初始化头部控制结构体)。显然,这个块是一个完整的块,也是一次能分配最大内存空间的块。之所以进行初始化,原因还是没有头部控制结构体的块无法进行管理。

三、代码及解释

1、malloc_init

 /**
* @brief Initializes malloc's gloabl varialbe and head of heap memory
* @param None
* @retval None
*/
void malloc_init(void)
{
mem_control_block * mcb = NULL; /* confirm heap's start address */
managed_memory_start = (unsigned int)malloc_addr;
/* confirm heap's size */
managed_memory_size = malloc_size;
/*confirm heap's end address */
managed_memory_end = managed_memory_start + managed_memory_size; /* make mcb point to heap's start address */
mcb = (mem_control_block *)managed_memory_start;
/*the first blcok is avaialbe */
mcb->is_available = ;
/*there is no block before the first block */
mcb->prior_blocksize = ;
/*the first block's block size is difference of between heap's size and control block */
mcb->current_blocksize = managed_memory_size - sizeof(mem_control_block);
/* Initialize done */
has_initialized = ;
}

2、malloc 

 /**
* @brief Dynamic distribute memory function
* @param numbytes: what size you need
* @retval a void pointer to the distribute first address
*/
void * malloc(unsigned int numbytes)
{
unsigned int current_location,otherbck_location;
/* This is the same as current_location, but cast to a memory_control_block */
mem_control_block * current_location_mcb = NULL,* other_location_mcb = NULL;
/* varialbe for saving return value and be set to 0 until we find something suitable */
void * memory_location = NULL;
/* current dividing block size */
unsigned int process_blocksize; /* Initialize if we haven't already done so */
if(! has_initialized) {
malloc_init();
} /* Begin searching at the start of managed memory */
current_location = managed_memory_start;
/* Keep going until we have searched all allocated space */
while(current_location != managed_memory_end){
/* current_location and current_location_mcb point to the same address. However,
* current_location_mcb is of the correct type, so we can use it as a struct. current_location
* is a void pointer so we can use it to calculate addresses.
*/
current_location_mcb = (mem_control_block *)current_location;
/* judge whether current block is avaiable */
if(current_location_mcb->is_available){
/* judge whether current block size exactly fit for the need */
if((current_location_mcb->current_blocksize == numbytes)){
/* It is no longer available */
current_location_mcb->is_available = ;
/* We own it */
memory_location = (void *)(current_location + sizeof(mem_control_block));
/* Leave the loop */
break;
/* judge whether current block size is enough for dividing a new block */
}else if(current_location_mcb->current_blocksize >= numbytes + sizeof(mem_control_block)){
/* It is no longer available */
current_location_mcb->is_available = ;
/* because we will divide current blcok,before we changed current block size,we should
* save the integral size.
*/
process_blocksize = current_location_mcb->current_blocksize;
/* Now blcok size could be changed */
current_location_mcb->current_blocksize = numbytes; /* find the memory_control_block's head of remaining block and set parameter,block of no
* parameter can't be managed.
*/
other_location_mcb = (mem_control_block *)(current_location + numbytes \
+ sizeof(mem_control_block));
/* the remaining block is still avaiable */
other_location_mcb->is_available = ;
/* of course,its prior block size is numbytes */
other_location_mcb->prior_blocksize = numbytes;
/* its size should get small */
other_location_mcb->current_blocksize = process_blocksize - numbytes \
- sizeof(mem_control_block); /* find the memory_control_block's head of block after current block and \
* set parameter--prior_blocksize.
*/
otherbck_location = current_location + process_blocksize \
+ sizeof(mem_control_block);
/* We need check wehter this block is on the edge of managed memeory! */
if(otherbck_location != managed_memory_end){
other_location_mcb = (mem_control_block *)(otherbck_location);
/* its prior block size has changed! */
other_location_mcb->prior_blocksize = process_blocksize\
- numbytes - sizeof(mem_control_block);
}
/*We own the occupied block ,not remaining block */
memory_location = (void *)(current_location + sizeof(mem_control_block));
/* Leave the loop */
break;
}
}
/* current block is unavaiable or block size is too small and move to next block*/
current_location += current_location_mcb->current_blocksize \
+ sizeof(mem_control_block);
}
/* if we still don't have a valid location,we'll have to return NULL */
if(memory_location == NULL) {
return NULL;
}
/* return the pointer */
return memory_location;
}

此函数流程图

3、free 

 /**
* @brief free your unused block
* @param firstbyte: a pointer to first address of your unused block
* @retval None
*/
void free(void *firstbyte)
{
unsigned int current_location,otherbck_location;
mem_control_block * current_mcb = NULL,* next_mcb = NULL,* prior_mcb \
= NULL,* other_mcb = NULL;
/* Backup from the given pointer to find the current block */
current_location = (unsigned int)firstbyte - sizeof(mem_control_block);
current_mcb = (mem_control_block *)current_location;
/* Mark the block as being avaiable */
current_mcb->is_available = ; /* find next block location */
otherbck_location = current_location + sizeof(mem_control_block) \
+ current_mcb->current_blocksize;
/* We need check wehter this block is on the edge of managed memeory! */
if(otherbck_location != managed_memory_end){
/* point to next block */
next_mcb = (mem_control_block *)otherbck_location;
/* We need check whether its next block is avaiable */
if(next_mcb->is_available){
/* Because its next block is also avaiable,we should merge blocks */
current_mcb->current_blocksize = current_mcb->current_blocksize \
+ sizeof(mem_control_block) + next_mcb->current_blocksize; /* We have merge two blocks,so we need change prior_blocksize of
* block after the two blocks,just find next block location.
*/
otherbck_location = current_location + sizeof(mem_control_block) \
+ current_mcb->current_blocksize;
/* We need check wehter this block is on the edge of managed memeory! */
if(otherbck_location != managed_memory_end){
other_mcb = (mem_control_block *)otherbck_location;
/* its prior block size has changed! */
other_mcb->prior_blocksize = current_mcb->current_blocksize;
}
}
} /* We need check wehter this block is on the edge of managed memeory! */
if(current_location != managed_memory_start){
/* point to prior block */
prior_mcb = (mem_control_block *)(current_location - sizeof(mem_control_block)\
- current_mcb->prior_blocksize);
/* We need check whether its prior block is avaiable */
if(prior_mcb->is_available){
/* Because its prior block is also avaiable,we should merge blocks */
prior_mcb->current_blocksize = prior_mcb->current_blocksize \
+ sizeof(mem_control_block) + current_mcb->current_blocksize; /* We have merge two blocks,so we need change prior_blocksize of
* block after the two blocks,just find next block location.
*/
otherbck_location = current_location + sizeof(mem_control_block) \
+ current_mcb->current_blocksize;
/* We need check wehter this block is on the edge of managed memeory! */
if(otherbck_location != managed_memory_end){
other_mcb = (mem_control_block *)otherbck_location;
/* its prior block size has changed! */
other_mcb->prior_blocksize = prior_mcb->current_blocksize;
}
}
}
}

知乎:

  释放一个已分配的数据块,只是对此数据块以及其前后数据块的控制块头部进行调整,并没有对实际有效数据区域(用户可见的区域)进行初始化操作,也就是说数据区域中的数据保持不变。

四、实现过程中易出现的Bug

  我们管理堆,靠的就是头部控制块。不管是分配还是释放,都是通过修改头部控制块的信息来实现堆链表的改变,从而达到从堆区中分配一块内存空间给用户,或者将一个内存块释放为重新可用。分配过程中的将大块一分为二,其实就是增加一个控制块头部;释放过程中的将两个连续可用块合并到一起,实际上就是减少一个控制块头部。

  堆栈的分配和释放,在我们改变堆栈控制头部的信息之前,一定要检查块是否是已经位于堆区之外(如果你确定这个块一定在堆内,可以省去检查代码)。倘若不去核查,而直接更改堆内存数据,有可能会更改堆区之外的数据,这是非常危险的。

五、测试结果

1、初始化建立第一个块成功

2、支持在第一个块上分割出一个用户块

3、用户块释放之后,可以和前后的可用块合并

4、释放后的块可以重新用于分配

5、支持在一个大的可用块中分出一个较小的用户块,伴随着一个新的较小块的产生

6、找不到合适的块分配给用户了,返回空地址

六、目前本程序的应用以及展望

1、目前本程序的应用

  操作系统的动态分配内存函数malloc的实现方法与本文差别还是很大的。目前本程序不支持操作系统的动态内存分配,但是可以在单片机的动态内存管理上使用。

  使用方法就是一开始为单片机定义一个固定位置、固定大小的堆区,然后将堆区的起始地址和大小作为宏参数传递给Rewrite_malloc_init函数中,并进行初始化。接下来,就可以调用malloc和free对内存进行动态分配和管理。

2、展望

  本程序的malloc函数,在没有足够的堆空间分配给用户时,就直接以NULL返回给用户。如果可能的话,在操作系统中,可以继续从操作系统中申请堆空间(sbrk函数),然后再进行分配。 

七、探秘malloc函数使用注意事项

1、使用malloc函数,注意对不用的空间进行释放

  显然,每申请一个用户空间都会引起可用堆区的减少。如果只申请不释放,就会导致可用堆区不断减少,到最后有可能就没有空间分配给新的用户需要。

2、释放只能一次,重复释放有可能产生错误

  多次释放同一用户空间,有可能不会出现错误;但是,也有可能会出现错误。也就是说,多次释放产生的结果是难以预测的。下面展示一个错误的释放例程。

/**
* @brief Test malloc function
* @param None
* @retval int
*/
int main(void)
{
int * p1,* p2,* p3; memset((void *)heap,,HEAPSIZE);
malloc_init(); p1 = (int *)malloc(sizeof(int));
p2 = (int *)malloc(sizeof(int));
p3 = (int *)malloc(sizeof(int)); free((void *)p2);
free((void *)p1);
free((void *)p2); while();
return ;
}

解释:

  可以肯定的是,p1、p2、p3这三个块对应的块大小都是相同的。一个块的大小,包含一个头部(sizeof(mem_control_block) = 12Byts)和有效数据区(4 Bytes),所以总共16个bytes。

  当第一次释放p2的时候,p2控制块的is_avaialbe变为1(重新可用)。当释放p1的时候,会将p1和p2整合到一起,这个时候p2对应的块就被注销了(注意块头部的信息还在内存中,只不过p1的块大小变大,将来沿着链表遍历时,会将这个块跳过)。当再次释放p2的时候,由于控制头部信息还在,所以还会再进行一次p1和p2块的整合,结果将p1块的大小设置成更大的,而超出了p1块实际拥有的空间。

  因此重复释放可能导致“块头部控制结构体”的错误修改,而导致块管理信息错误,丧失堆区管理功能。

  

参考资料:浅析malloc()的几种实现方式

源码及VC6.0工程:malloc.zip

如何实现一个malloc函数的更多相关文章

  1. 关于malloc函数的动态分配问题

    malloc函数动态分配了一个整型的内存空间,让abc都指向刚申请的空间,所以只有最后一个赋值语句的值保留在了空间里 #include<stdio.h> main() { int *a,* ...

  2. 转:如何实现一个malloc

    如何实现一个malloc 转载后排版效果很差,看原文!   任何一个用过或学过C的人对malloc都不会陌生.大家都知道malloc可以分配一段连续的内存空间,并且在不再使用时可以通过free释放掉. ...

  3. malloc 函数工作机制(转)

    malloc()工作机制 malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表.调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块.然后,将 ...

  4. malloc函数详解

    一.原型:extern void *malloc(unsigned int num_bytes); 头文件:#include <malloc.h> 或 #include <alloc ...

  5. malloc 函数到底做了什么?

    请看下面的代码. 猜测结果是什么?编译通过吗? #include <stdio.h> #include <stdlib.h> int main() { ; char *ptr ...

  6. malloc函数和其他内存分配函数

    1. 需要包含头文件:#i nclude 或 #i nclude 函数声明(函数原型): void *malloc(int size); 说明:malloc 向系统申请分配指定size个字节的内存空间 ...

  7. 如何实现一个malloc

    任何一个用过或学过C的人对malloc都不会陌生.大家都知道malloc可以分配一段连续的内存空间,并且在不再使用时可以通过free释放掉.但是,许多程序员对malloc背后的事情并不熟悉,许多人甚至 ...

  8. malloc函数

    C语言中,使用malloc函数向内存中动态申请空间. 函数的原型是extern void *malloc(unsigned int num_bytes); 可见,函数返回的是指针类型,参数是要申请的空 ...

  9. malloc函数的底层实现你是否清楚

    malloc函数的底层实现你是否清楚 说起malloc函数,每个人都能说出它的功能,而且我们经常会用到,那么今天我要说的是关于malloc函数在编译器的底层实现,如果你对它的实现已经很清楚了,那么你可 ...

随机推荐

  1. python列表删除重复元素的三种方法

    给定一个列表,要求删除列表中重复元素. listA = ['python','语','言','是','一','门','动','态','语','言'] 方法1,对列表调用排序,从末尾依次比较相邻两个元素 ...

  2. eclipse git插件配置

    一.Eclipse上安装GIT插件EGit Eclipse的版本eclipse-java-helios-SR2-win32.zip(在Eclipse3.3版本找不到对应的 EGit插件,无法安装) E ...

  3. [转]在BBB启动时自动加载dtbo(或执行脚本、运行程序)

    启动时自动加载dtbo,实际上就是做了一个cape.官方推荐的方法是用eeprom来实现,请参考我的博文<为BBB制作专属自己的cape(一)>和<为BBB制作专属自己的cape(四 ...

  4. SurfaceView的使用

    1.概念 SurfaceView是View类的子类,可以直接从内存或者DMA等硬件接口取得图像数据,是个非常重要的绘图视图.它的特性是:可以在主线程之外的 线程中向屏幕绘图上.这样可以避免画图任务繁重 ...

  5. Unix网络编程(1)——socket一窥

    套接口地址结构 IPv4的套接口地址结构为: struct sockaddr_in { uint8_t sin_len; sa_family_t sin_family; struct in_addr ...

  6. 【排障】Outlook Express 2G收件箱大小限制

    Outlook Express 2G收件箱大小限制 文:铁乐猫 ----------------------------- Outlook Express(以下简称OE)客户端收件箱大于或接近2G时, ...

  7. JSON对象和string的相互转换

    JSON.stringify(obj) 将JSON转为字符串. JSON.parse(string) 将字符串转为JSON格式.

  8. 深入解析hasOwnProperty与isPrototypeOf

    这里采用一个实例来说明: function Person(name) { //以下都是Person的OwnProperty this.name = name; this.showMe = functi ...

  9. Apache Mesos总体架构

    http://developer.51cto.com/art/201401/426507.htm 1. 前言 同其他大部分分布式系统一样,Apache Mesos为了简化设计,也是采用了master/ ...

  10. spring beans的写入工具——spring-beans-writer

    spring-beans-writer是我曾经为动态生成spring beans配置文件做的一个写入工具,托管地址: https://github.com/bluejoe2008/spring-bea ...