`

malloc函数的一种简单的原理性实现[转]

    博客分类:
  • C++
阅读更多

malloc()是 C 语言中动态存储管理的一组标准库函数之一。其作用是在内存的动态存储区中分配一个长度为 size 的连续空间。其参数是一个无符号整形数,返回值是一个指向所分配的连续存储域的起始地址的指针

malloc()工作机制

   malloc函数的实质体现在,它有一个将可 用的内存块连接为一个长长的列表的所谓空闲链表。调用 malloc 函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分 为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连 接表上。调用 free 函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空 闲链上可能没有可以满足用户要求的片段了。于是, malloc 函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲 块合并成较大的内存块。 

malloc()在操作系统中的实现

  在  程序中,多次使用 malloc ()  和  free() 。不过,您可能没有用一些时间去思考它们在您的操作系统中是如何实现的。本节将向您展示  malloc  和  free  的一个最简化实现的代码,来帮助说明管理内存时都涉及到了哪些事情。
  在大部分操作系统中,内存分配由以下两个简单的函数来处理:
  void *malloc (long numbytes) :该函数负责分配  numbytes  大小的内存,并返回指向第一个字节的指针。
  void free(void *firstbyte) :如果给定一个由先前的  malloc  返回的指针,那么该函数会将分配的空间归还给进程的 空闲空间

  malloc_init  将是初始化内存分配程序的函数。它要完成以下三件事:将分配程序标识为已经初始化,找到系统中最后一个有效内存地址,然后建立起指向我们管理的内存的指针。这三个变量都是全局变量:



         //清单  1.  我们的简单分配程序的全局变量

         int  has_initialized = 0;
         void  *managed_memory_start;
         void  *last_valid_address; 如前所述,被映射的内存的边界(最后一个有效地址)常被称为系统中断点或者 当前中断点。在很多 UNIX?  系统中,为了指出当前系统中断点,必须使用  sbrk(0)  函数。  sbrk  根据参数中给出的字节数移动当前系统中断点,然后返回新的系统中断点。使用参数  只是返回当前中断点。这里是我们的  malloc  初始化代码,它将找到当前中断点并初始化我们的变量:



清单 2.  分配程序初始化函数
/* Include the sbrk function */
 
#include 
void  malloc_init()
{
/* grab the last valid address from the OS */
last_valid_address = sbrk(0);
/* we don''t have any memory to manage yet, so
 *just set the beginning to be last_valid_address
 */
managed_memory_start = last_valid_address;
/* Okay, we''re initialized and ready to go */
 has_initialized = 1;
} 现在,为了完全地管理内存,我们需要能够追踪要分配和回收哪些内存。在对内存块进行了 free  调用之后,我们需要做的是诸如将它们标记为未被使用的等事情,并且,在调用  malloc  时,我们要能够定位未被使用的内存块。因此,  malloc  返回的每块内存的起始处首先要有这个结构:



//清单  3.  内存控制块结构定义
struct  mem_control_block {
     int  is_available;
     int  size;
}; 现在,您可能会认为当程序调用 malloc  时这会引发问题  ——  它们如何知道这个结构?答案是它们不必知道;在返回指针之前,我们会将其移动到这个结构之后,把它隐藏起来。这使得返回的指针指向没有用于任何其他用途的 内存。那样,从调用程序的角度来看,它们所得到的全部是空闲的、开放的内存。然后,当通过  free()  将该指针传递回来时,我们只需要倒退几个内存字节就可以再次找到这个结构。

 

 

整个的结构图如下:

 

 

图1 malloc管理结构图

 

  在讨论分配内存之前,我们将先讨论释放,因为它更简单。为了释放内存,我们必须要做的惟一一件事情就是,获得我们给出的指针,回退 sizeof(struct mem_control_block)  个字节,并将其标记为可用的。这里是对应的代码:



清单 4.  解除分配函数
void  free( void  *firstbyte) {
     struct  mem_control_block *mcb;
/* Backup from the given pointer to find the
 * mem_control_block
 */
   mcb = firstbyte -  sizeof ( struct  mem_control_block);
/* Mark the block as being available */
  mcb->is_available = 1;
/* That''s It!  We''re done. */
   return ;
} 如您所见,在这个分配程序中,内存的释放使用了一个非常简单的机制,在固定时间内完成内存释放。分配内存稍微困难一些。我们主要使用连接的指针遍历内存来寻找开放的内存块。这里是代码:



//清单  6.  主分配程序
void  *malloc( long  numbytes) {
     /* Holds where we are looking in memory */
     void  *current_location;
     /* This is the same as current_location, but cast to a
    * memory_control_block
    */
     struct  mem_control_block *current_location_mcb;
     /* This is the memory location we will return.  It will
    * be set to 0 until we find something suitable
    */
     void  *memory_location;
     /* Initialize if we haven''t already done so */
     if (! has_initialized) {
        malloc_init();
    }
     /* The memory we search for has to include the memory
    * control block, but the users of malloc don''t need
    * to know this, so we''ll just add it in for them.
    */
    numbytes = numbytes +  sizeof ( struct  mem_control_block);
     /* Set memory_location to 0 until we find a suitable
    * location
    */
    memory_location = 0;
     /* 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 != last_valid_address)
    {
     /* 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 =
            ( struct  mem_control_block *)current_location;
         if (current_location_mcb->is_available)
        {
             if (current_location_mcb->size >= numbytes)
            {
             /* Woohoo!  We''ve found an open,
            * appropriately-size location.
                */
                 /* It is no longer available */
                current_location_mcb->is_available = 0;
                 /* We own it */
                memory_location = current_location;
                 /* Leave the loop */
                 break ;
            }
        }
         /* If we made it here, it''s because the Current memory
        * block not suitable; move to the next one
        */
        current_location = current_location +
            current_location_mcb->size;
    }
     /* If we still don''t have a valid location, we''ll
    * have to ask the operating system for more memory
    */
     if (! memory_location)
    {
         /* Move the program break numbytes further */
        sbrk(numbytes);
         /* The new memory will be where the last valid
        * address left off
        */
        memory_location = last_valid_address;
         /* We''ll move the last valid address forward
        * numbytes
        */
        last_valid_address = last_valid_address + numbytes;
         /* We need to initialize the mem_control_block */
        current_location_mcb = memory_location;
        current_location_mcb->is_available = 0;
        current_location_mcb->size = numbytes;
    }
     /* Now, no matter what (well, except for error conditions),
    * memory_location has the address of the memory, including
    * the mem_control_block
    */
     /* Move the pointer past the mem_control_block */
    memory_location = memory_location +  sizeof ( struct  mem_control_block);
     /* Return the pointer */
     return  memory_location;
 } 这就是我们的内存管理器。现在,我们只需要构建它,并在程序中使用它即可. 多次调用 malloc ()后空闲内存被切成很多的小内存片段,这就使得用 户在申请内存使用时,由于找不到足够大的内存空间, malloc ()需要进行内存整理,使得函数的性能越来越低。聪明的程序员通过总是分配大小为 2 的幂的 内存块,而最大限度地降低潜在的 malloc 性能丧失。也就是说,所分配的内存块大小为 4 字节、 8 字节、 16 字节、  18446744073709551616 字节,等等。这样做最大限度地减少了进入空闲链的怪异片段(各种尺寸的小片段都有)的数量。尽管看起来这好像浪 费了空间,但也容易看出浪费的空间永远不会超过 50%

 

---------------------------------------------------------------------------------------------

malloc函数
函数声明( 函数原型 )
void *malloc(int size);
说明:malloc  向系统申请分配指定 size 个字节的内存空间。返回类型是  void*  类型。 void*  表示未确定类型的指针。 C,C++ 规定, void*  类型可以强制转换为任何其它类型的指针。
从函数声明上可以看出。malloc  和  new  至少有两个不同 : new  返回指定类型的指针,并且可以自动计算所需要大小。比如:
int *p;
p = new int; //返回类型为 int*  类型 ( 整数型指针 ) ,分配大小为  sizeof(int);
或:
int* parr;
parr = new int [100]; //返回类型为  int*  类型 ( 整数型指针 ) ,分配大小为  sizeof(int) * 100;
而 malloc  则必须由我们计算要字节数,并且在返回后强行转换为实际类型的指针。
int* p;
p = (int *) malloc (sizeof(int));
第一、malloc  函数返回的是  void *  类型,如果你写成: p = malloc (sizeof(int));  则程序无法通过编译,报错: 不能将  void*  赋值给  int *  类型变量 。所以必须通过  (int *)  来将强制转换。
第二、函数的实参为 sizeof(int)  ,用于指明一个整型数据需要的大小。如果你写成:
int* p = (int *) malloc (1);
代码也能通过编译,但事实上只分配了1 个字节大小的内存空间,当你往里头存入一个整数,就会有 3 个字节无家可归,而直接 住进邻居家 !造成的结果是后面的内存中原有数据内容全部被清空。
malloc 也可以达到  new []  的效果,申请出一段连续的内存,方法无非是指定你所需要内存大小。
比如想分配100 int 类型的空间:
int* p = (int *) malloc ( sizeof(int) * 100 ); //分配可以放得下 100 个整数的内存空间。
另外有一点不能直接看出的区别是,malloc  只管分配内存,并不能对所得的内存进行初始化,所以得到的一片新内存中,其值将是随机的。
除了分配及最后释放的方法不一样以外,通过malloc new 得到指针,在其它操作上保持一致。

 

--------------------------------------------------------------------------------------------

有了malloc/free  为什么还要 new/delete 
malloc  free  C++/C  语言的标准库函数, new/delete  C++ 的运算符。它们都可
用于申请动态内存和释放内存。
对于非内部数据类型的对象而言,光用maloc/free  无法满足动态对象的要求。对象
在创建的同时要自动执行构造函数, 对象在消亡之前要自动执行析构函数。由于
malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数
和析构函数的任务强加于malloc/free
因此C++ 语言需要一个能完成动态内存分配和初始化工作的运算符 new ,以及一个
能完成清理与释放内存工作的运算符delete 。注意 new/delete  不是库函数。
我们先看一看malloc/free  new/delete  如何实现对象的动态内存管理,见示例 7-8
class Obj
{
public :
Obj(void){ cout << “Initialization” << endl; }
~Obj(void){ cout << “Destroy” << endl; }
void Initialize(void){ cout << “Initialization” << endl; }
void Destroy(void){ cout << “Destroy” << endl; }
};
void UseMallocFree(void)
{
Obj *a = (obj *)malloc(sizeof(obj)); // 申请动态内存
a->Initialize(); // 初始化
//…
a->Destroy(); // 清除工作
free(a); // 释放内存
}
void UseNewDelete(void)
{
Obj *a = new Obj; // 申请动态内存并且初始化
//…
delete a; // 清除并且释放内存
}
示例7-8  malloc/free  new/delete  如何实现对象的动态内存管理
Obj  的函数 Initialize  模拟了构造函数的功能,函数 Destroy  模拟了析构函数的功
能。函数UseMallocFree  中,由于 malloc/free  不能执行构造函数与析构函数,必须调用
成员函数Initialize  Destroy  来完成初始化与清除工作。函数 UseNewDelete  则简单得

多。
所以我们不要企图用malloc/free  来完成动态对象的内存管理,应该用 new/delete

分享到:
评论

相关推荐

    MallocLab实验-计算机系统基础-gddrxy

    例如C程序通过调用malloc函数来分配一个块,通过调用free函数来释放一个块。其中malloc采用的总体策略是:先系统调用sbrk一次,会得到一段较大的并且是连续的空间。进程把系统内核分配给自己的这段空间留着慢慢用。...

    -C++参考大全(第四版) (2010 年度畅销榜

    29.3 malloc函数 29.4 realloe函数 第30章 实用函数 30.1 abort函数 30.2 abs函数 30.3 assert函数 30.4 atexit函数 30.5 atof函数 30.6 atoi函数 30.7 atol函数 30.8 bsearch函数 30.9 div函数 30.10 exit函数 30....

    linux系统编程之线程.zip

    如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止,由于从main函数return也相当于调用exit,为了防止新创建的线程还没有得到执行就终止,我们在main函数return之前延时1秒,这只是一种权宜之计,即使...

    net学习笔记及其他代码应用

    10.求以下表达式的值,写出您想到的一种或几种实现方法: 1-2+3-4+……+m [Page] 答: int Num = this.TextBox1.Text.ToString() ; int Sum = 0 ; for (int i = 0 ; i ; i++) { if((i%2) == 1) { Sum += i ; ...

    C 程序指导书及实践指导

    1、输入一个简单的C语言程序:输入矩形的两条边,求矩形的面积。 [分析与讨论] 1、记下在调试过程中所发现的错误、系统给出的出错信息和对策。分析讨论对策成功或失败的原因。 2、总结C程序的结构和书写规则。 ...

    一些C面试题,希望能对大家有帮助

    c面试题 4. static有什么用途?(请至少说明两种) 1.限制变量的作用域 2.设置变量的存储域 7. 引用与指针有什么区别?...3.1号信令和7号信令有什么区别,我国某前广泛使用的是那一种? 4.列举5种以上的电话新业务?

    vld(Visual Leak Detector 内存泄露检测工具 源码)

    Visual Leak Detector使用了一种方法来获得当前的程序计数器。首先,它调用一个函数,则这个函数的返回地址就是当前的程序计数器,而函数的返回地址可以很容易的从堆栈中拿到。下面是Visual Leak Detector获得当前...

    c++面试题基础分享.doc

    9.编写my_strcpy函数,实现与库函数strcpy类似的功能,不能使用任何库函数 10.请讲述堆和栈的区别 11.全局变量和局部变量有什么区别?实怎么实现的?操作系统和编译器是怎么知道的 12.new、delete、malloc、free...

    二叉排序树与平衡二叉树的实现

    二叉排序树是一种动态树表。其特点是:树的结构通常不是一次生成的,而是在查找过程中,当树中不存在关键字等于给定值的节点时再进行插入。新插入的结点一定是一个新添加的叶子节点,并且是查找不成功时查找路径上...

    77G 22套C语言 C++ 数据结构 程序设计视频课程合集 C丨C++相关学习视频全套视频教程

    066.WS_Socket_编程原理.mp4 067.WS_TCP_Socket.mp4 068.WS_TCP_Socket_Client.mp4 069.WS_UDP_Socket_Receiver.mp4 070.WS_UDP_Socket_Sender.mp4 071.MFC_抓取网页.mp4 072.MFC_HOOK.mp4 073.MFC_全局键盘...

    进程的调度

    实验原理 每个进程用一个进程控制块( PCB)表示。进程控制块可以包含进程名、优先级、到达时间、需要运行时间、已用CPU时间、进程状态等等。 进程的运行时间以时间片为单位进行计算。 每个进程的状态可以是就绪 W...

    Linux多线程服务端编程:使用muduo C++网络库

    《Linux多线程服务端编程:使用muduo C++网络库》主要讲述采用现代C++在x86-64 Linux上编写多线程TCP网络服务程序的主流常规技术,重点讲解一种适应性较强的多线程服务器的编程模型,即one loop per thread。...

    uboott移植实验手册及技术文档

    nand_init()函数在两个文件中实现。其调用与 CFG_NAND_LEGACY 宏有 关,如果没有定义这个宏,系统调用 drivers/nand/nand.c 中的 nand_init();否则调用自己在 本文件中的 nand_init()函数,本例使用后者。fs2410.c...

Global site tag (gtag.js) - Google Analytics