浅析C\C++的动态内存管理

作者:左懒
时间:2013.5.13

声明: 

  原创文章,转载请标明原文链接。

  个人能力有限,文章可能存在多处错误。如果您发现文中有不足或错误之处敬请批评指针。我的邮箱是: zuolanaill@gmail.com,欢迎您邮件斧正。

  本文内容参考了KEIL C51和VS2012中的部分源码, 并对其进行了简单的分析和探讨,其中不乏有不确切之处,望您的批评指正。

  在不同的操作系统中,C\C++的内存管理实现可能并不相同,因此本文所介绍的内容可能与您需要的内容有所出入,本文仅供参考学习。

一、KEIL IDE 中的动态内存管理

  1. KEIL IDE 安装目录中的 *\Keil\C51\LIB目录下,包含了 malloc.c , calloc.c, free.c, realloc.c, init_mem.c 等源码文件,其中内容便是KEIL IDE工具提供的动态内存分配的函数。 在这里,我挑了几源文件进行了简单的分析,其中包含init_mem.c 、malloc.c 、free.c。 其它的源码文件或多或少都以这几个源码文件中的数据结构和函数进行扩充,所以也就没必要深入去研究了。

  2. 首先先分析一下init_mem.c 这个文件。其中包含了一个最重要的内存池初始化函数: int init_mempool (void  *pool, unsigned int size);

  在分析这个函数之前,先介绍一个维护管理其动态内存的一个链表,这个链表重中之重,在C51单片机开发中,调用malloc和free进行内存的分配和释放全靠这个链表。其实这个数据结构挺简单的,只有两个数据成员。下面给出这个数据结构的定义:

1 struct __mem__
2 {
3     struct __mem__    *next;   /* 用于link堆内存中的空闲块 */
4     unsigned int    len;        /* 下一个内存块的长度 */
5 };

OK, 接下来我们看一下管理整个堆内存的头结点, 它是一个全局的变量。声明在init_mem.c 的源文件中。

1 typedef struct __mem__         __memt__;
2 typedef __memt__  *__memp__;
3 
4 __memt__  __mem_avail__ [2] =
5 { 
6     { NULL, 0 },    /*    用于管理堆内存的头结点,当调用完init_mempool()之后会指向首块内存块,如果堆内存已经用完,则__mem_avail__[0].next为NULL */
7     { NULL, 0 },    /* UNUSED but necessary so free doesn't join HEAD or ROVER with the pool */
8                     /*    原代码的解释如上,目前我还搞不懂这个节点有什么用    */
9 };

好,现在介绍一下重点的东西。现在看一下内存池的初始化函数,其实它的工作很简单。不过记住,内存池初始化函数只能够被调用一次。多次调用的话上几次的内存将无法被释放。

 1 /*    初始化内存池,    */
 2 int init_mempool (void  *pool, unsigned int size)
 3 {
 4     
 5     if (size < MIN_POOL_SIZE)    /*    如果内存小于定义最小的内存池,则返回内存池初始化失败    */
 6         return (0);     
 7 
 8 
 9     if (pool == NULL)
10     {
11         pool = (void *)1;       /*    如果内存池传入的是NULL,则将内存池指向地址为1的位置。    *
12                                 /*    因为C51单片机的数据段是存储在XDATA, 大小为0x0 ~ 0xffff 共64K可用    */
13         size--;                 /*    取size-1的内存大小作为堆内存    */
14                                 /*    注意的是,若是在X86兼容机不能这么写,因为X86的机子是有内存保护的    */
15     }
16 
17 
18     AVAIL.next = (__memp__)pool;        /*    指向堆内存    */
19     AVAIL.len  = size;                  /*    堆内存大小    */
20 
21 
22     (AVAIL.next)->next = NULL;            /*    当前的堆内存还是一块空闲的内存    */
23     (AVAIL.next)->len  = size - HLEN;     /*    除去管理堆内存的头结点信息,还剩下多少空间可用    */
24 
25     return (-1);                          /*    返回成功    */
26 }

看完这个其实内存初始化的工作还是挺简单的。下面我给出调用 init_mempool()后头结点大概的示意图(画得有点戳,请见谅):

 

看过图应该还是挺直观的。

  如果您你自己查看KEIL 安装目录里的 init_mem.c 源码会发现跟我贴出来的内容有所区别, 它可能如下:

 1 /*-----------------------------------------------------------------------------
 2 INIT_MEM.C is part of the C51 Compiler package from Keil Software.
 3 Copyright (c) 1995-2002 Keil Software.  All rights reserved.
 4 -----------------------------------------------------------------------------*/
 5 #include <stdlib.h>
 6 
 7 /*-----------------------------------------------
 8 Memory pool block structure and typedefs.
 9 Memory is laid out as follows:
10 
11 {[NXT|LEN][BLK (LEN bytes)]}{[NXT|LEN][BLK]}...
12 
13 Note that the size of a node is:
14 __mem__.len + sizeof (__mem__)
15 -----------------------------------------------*/
16 struct __mem__
17 {
18     struct __mem__ _MALLOC_MEM_ *next;    /* single-linked list */
19     unsigned int                len;      /* length of following block */
20 };
21 
22 typedef struct __mem__         __memt__;
23 typedef __memt__ _MALLOC_MEM_ *__memp__;
24 
25 #define    HLEN    (sizeof(__memt__))
26 
27 /*-----------------------------------------------
28 Memory pool headers.  AVAIL points to the first
29 available block or is NULL if there are no free
30 blocks.  ROVER is a roving header that points to
31 a block somewhere in the list.
32 
33 Note that the list is maintained in address
34 order.  AVAIL points to the block with the
35 lowest address.  That block points to the block
36 with the next higher address and so on.
37 -----------------------------------------------*/
38 __memt__ _MALLOC_MEM_ __mem_avail__ [2] =
39 { 
40     { NULL, 0 },    /* HEAD for the available block list */
41     { NULL, 0 },    /* UNUSED but necessary so free doesn't join HEAD or ROVER with the pool */
42 };
43 
44 #define AVAIL    (__mem_avail__[0])
45 
46 #define MIN_POOL_SIZE    (HLEN * 10)
47 
48 /*-----------------------------------------------------------------------------
49 int init_mempool (
50 void _MALLOC_MEM_ *pool,    address of the memory pool
51 unsigned int size);           size of the pool in bytes
52 
53 Return Value
54 ------------
55 0        FAILURE:  Memory pool is not large enough
56 NZ        SUCCESS:  Memory pool management initialized.
57 -----------------------------------------------------------------------------*/
58 int init_mempool (
59     void _MALLOC_MEM_ *pool,
60     unsigned int size)
61 {
62     /*-----------------------------------------------
63     Verify that the pool is large enough to actually
64     do something.  If it is too small, exit with 0.
65     -----------------------------------------------*/
66     if (size < MIN_POOL_SIZE)
67         return (0);                     /* FAILURE */
68 
69     /*-----------------------------------------------
70     If the pool points to the beginning of a memory
71     area (NULL), change it to point to 1 and decrease
72     the pool size by 1 byte.
73     -----------------------------------------------*/
74     if (pool == NULL)
75     {
76         pool = 1;
77         size--;
78     }
79 
80     /*-----------------------------------------------
81     Set the AVAIL header to point to the beginning
82     of the pool and set the pool size.
83     -----------------------------------------------*/
84     AVAIL.next = pool;
85     AVAIL.len  = size;
86 
87     /*-----------------------------------------------
88     Set the link of the block in the pool to NULL
89     (since it's the only block) and initialize the
90     size of its data area.
91     -----------------------------------------------*/
92     (AVAIL.next)->next = NULL;
93     (AVAIL.next)->len  = size - HLEN;
94 
95     return (-1);                       /* SUCCESS */
96 }

 KEIL 工具所带的源文件跟我贴出来的代码有所不同。其原因如下:

  第一:其中在数据结构的声明和头结点的定义都多出了一个  _MALLOC_MEM_ , 但是这个_MALLOC_MEM_标识符在我的KEIL中是没办法跟踪出来的,据我个人理解这个_MALLOC_MEM_ 可能是用#define _MALLOC_MEM_ 定义的一个空的宏定义,用于标识这个数据结构用于动态分配所用。所以在我贴出来的代码中去除了这个_MALLOC_MEM_标识符。尽量让代码看起来简洁舒服。

  第二: 里面一些不同类型的指针直接赋值也被我用上了强制转换后再赋值,避免某些编译器会报错。

  3. malloc.c 源文件分析

    在malloc.c源文件中malloc函数原型是:void * malloc(unsigned int size); size为需要申请动态内存的字节大小。但是实际上它在分配内存的时候不仅仅只分配size个字节内存,它还会多为它分配一个管理这个分配内存块的管理节点。所以它分配的内存字节数应该是: size+sizeof(__mem__); 其中__mem__便是init_mem.c源文件中的链表节点。

    malloc的算法大概是:查找AVAIL链表中next域下各个节点空闲的内存块,如果发现这个空闲块>=size的大小,则停止查找并进行内存分配。如果找不到合适的内存块,则返回NULL。

    如果所找到的空闲块分配size个字节之后所剩的内存并不多,则将空闲块分配给应用程序后,然后将这一块从AVAIL链表中直接除去。如果空闲块所剩较多,则分割这个空闲块,将分割出来的空闲块链在AVAIL链表中。

    malloc 的源码如下:

 

 1 void * _malloc ( unsigned int size)
 2 {
 3     __memp__ q;                /* 指向的是内存空闲块 */
 4     __memp__ p;                /* q->next */
 5     unsigned int k;            /* 剩下的空闲内存块大小 */
 6 
 7 
 8     q = &AVAIL;
 9 
10 
11     /*    在AVATL链表中查找空闲的堆内存    */
12     while (1)
13     {
14         if ((p = q->next) == NULL)
15         {
16             return (NULL);                             /* 没有空闲的堆内存,返回NULL */
17         }
18 
19         if (p->len >= size)                            /*    找到一个空闲的内存块,并且这个内存块大于所要分配的大小    */
20             break;
21 
22         q = p;
23     }
24 
25     k = p->len - size;                                /* 申请内存后还剩下多少内存 */
26 
27     if (k < MIN_BLOCK)                                /* 如果分配的内存太大,超过了最小堆内存块的定义,则一次性把堆内存给分配完,并把AVAIL结点的next域置NULL,表示没有堆内存可再次分配了 */
28     {
29         q->next = p->next;
30         return (&p[1]);                                /* 返回分配的内存指针 */
31     }
32 
33 
34     k -= HLEN;                                        /*    多分配一个管理分配内存的节点,所以k要多减去一个节点的字节数    */
35     p->len = k;                                       /*    p所指的是空闲块    */
36 
37     
38     q = (__memp__) (((char *) (&p [1])) + k);        /*    q指向分配内存的起始地址。注意,这里的q所指向的地址是有加上管理分配内存节点的。    */
39     q->len = size;                                   /*    分配内存的大小,也就是malloc 参数中size的大小,它不包含管理节点的大小    */
40 
41     return (&q[1]);                                  /* 返回分配的内存的指针 */
42 }

    当你调用 int *p = (int *)malloc(40); 之后它的内存分布情况可能如下: 

 

  malloc.c源码分析就此告一段落,因为malloc.c源文件中主要也是这个malloc函数。当然,下面我会给出KEIL 安装目录下malloc.c的源码供大家参考对比:

 

  1 /*-----------------------------------------------------------------------------
  2 MALLOC.C is part of the C51 Compiler package from Keil Software.
  3 Copyright (c) 1995-2002 Keil Software.  All rights reserved.
  4 -----------------------------------------------------------------------------*/
  5 #include <stdlib.h>
  6 
  7 /*-----------------------------------------------
  8 Memory pool block structure and typedefs.
  9 Memory is laid out as follows:
 10 
 11 {[NXT|LEN][BLK (LEN bytes)]}{[NXT|LEN][BLK]}...
 12 
 13 Note that the size of a node is
 14 __mem__.len + sizeof (__mem__)
 15 -----------------------------------------------*/
 16 struct __mem__
 17 {
 18     struct __mem__ _MALLOC_MEM_ *next;    /* single-linked list */
 19     unsigned int                len;      /* length of following block */
 20 };
 21 
 22 typedef struct __mem__         __memt__;
 23 typedef __memt__ _MALLOC_MEM_ *__memp__;
 24 
 25 #define    HLEN    (sizeof(__memt__))
 26 
 27 /*-----------------------------------------------
 28 Memory pool headers.  AVAIL points to the first
 29 available block or is NULL if there are no free
 30 blocks.
 31 
 32 Note that the list is maintained in address
 33 order.  AVAIL points to the block with the
 34 lowest address.  That block points to the block
 35 with the next higher address and so on.
 36 -----------------------------------------------*/
 37 extern __memt__ _MALLOC_MEM_ __mem_avail__ [];
 38 
 39 #define AVAIL    (__mem_avail__[0])
 40 
 41 #define MIN_BLOCK    (HLEN * 4)
 42 
 43 /*-----------------------------------------------------------------------------
 44 void _MALLOC_MEM_ *malloc (
 45 unsigned int size);            number of bytes to allocate
 46 
 47 Return Value
 48 ------------
 49 NULL    FAILURE:  No free blocks of size are available
 50 NON-NULL    SUCCESS:  Address of block returned
 51 -----------------------------------------------------------------------------*/
 52 void _MALLOC_MEM_ *malloc (
 53                            unsigned int size)
 54 {
 55     __memp__ q;            /* ptr to free block */
 56     __memp__ p;            /* q->next */
 57     unsigned int k;            /* space remaining in the allocated block */
 58 
 59     /*-----------------------------------------------
 60     Initialization:  Q is the pointer to the next
 61     available block.
 62     -----------------------------------------------*/
 63     q = &AVAIL;
 64 
 65     /*-----------------------------------------------
 66     End-Of-List:  P points to the next block.  If
 67     that block DNE (P==NULL), we are at the end of
 68     the list.
 69     -----------------------------------------------*/
 70     while (1)
 71     {
 72         if ((p = q->next) == NULL)
 73         {
 74             return (NULL);                /* FAILURE */
 75         }
 76 
 77         /*-----------------------------------------------
 78         Found Space:  If block is large enough, reserve
 79         if.  Otherwise, copy P to Q and try the next
 80         free block.
 81         -----------------------------------------------*/
 82         if (p->len >= size)
 83             break;
 84 
 85         q = p;
 86     }
 87 
 88     /*-----------------------------------------------
 89     Reserve P:  Use at least part of the P block to
 90     satisfy the allocation request.  At this time,
 91     the following pointers are setup:
 92 
 93     P points to the block from which we allocate
 94     Q->next points to P
 95     -----------------------------------------------*/
 96     k = p->len - size;        /* calc. remaining bytes in block */
 97 
 98     if (k < MIN_BLOCK)        /* rem. bytes too small for new block */
 99     {
100         q->next = p->next;
101         return (&p[1]);                /* SUCCESS */
102     }
103 
104     /*-----------------------------------------------
105     Split P Block:  If P is larger than we need, we
106     split P into two blocks:  the leftover space and
107     the allocated space.  That means, we need to
108     create a header in the allocated space.
109     -----------------------------------------------*/
110     k -= HLEN;
111     p->len = k;
112 
113     q = (__memp__ ) (((char _MALLOC_MEM_ *) (&p [1])) + k);
114     q->len = size;
115 
116     return (&q[1]);                    /* SUCCESS */
117 }

  实际上里面核心的东西就malloc这个函数而已。

  4. free.c源文件的分析

    free函数在free.c源文件中的声明是: void _free ( void  *memp); 其中memp参数是要进行内存释放的指针。

    free函数的基本算法是:在AVAIL链表中查找一个合适的节点,然后将它挂到链表中。在将memp挂到链表之前,会检查memp相邻之间是否有空闲的内存块,如果有,则将两则合并后再挂到链表中,如果没有,则直接挂到链表中。

  free函数源码如下:

void _free ( void  *memp)
{

    __memp__ q;                    /* 指向空闲块 */
    __memp__ p;                    /* q->next */
    __memp__ p0;                /* 要被释放的指针 */

    if ((memp == NULL) || (AVAIL.len == 0))
        return;

    p0 = (__memp__)memp;
    p0 = &p0 [-1];    


    q = &AVAIL;



    /*    查找一个可以挂载memp的合适的挂载节点    */
    /*    经过这个算法查找,空闲内存块的开始地址会按升序排序    */
    while (1)
    {
        p = q->next;

        if ((p == NULL) || (p > memp))
            break;

        q = p;
    }


    /*    memp之后是否有空闲内存,若有则合并内存块        */
    if ((p != NULL) && ((((char *)memp) + p0->len) == (char *)p))
    {
        p0->len += p->len + HLEN;
        p0->next = p->next;
    }
    else
    {
        p0->next = p;
    }

    /*    memp之前是否有空闲内存,若有则合并后挂到链表中,若没有则直接挂到链表    */
    if ( ( ((char  *)q) + q->len + HLEN ) == (char *)p0 )
    {
        q->len += p0->len + HLEN;
        q->next = p0->next;
    }
    else
    {
        q->next = p0;
    }
}

int *p = (int *)_malloc (sizeof(int)*10);
int *q = (int *)_malloc (sizeof(int));
_free (p);

当调用以上代码之后,它的内存分布情况可能如下: 

 

从代码之中也可以发现,内存释放之后,管理p内存块的内容并不会做任何处理。所以下一次分配内存的时候再被分配到这一块内存的时候,内存的内容还可能残留着上一次分配后使用过的内容。

下面给出free.c中的源码内容

  1 /*-----------------------------------------------------------------------------
  2 FREE.C is part of the C51 Compiler package from Keil Software.
  3 Copyright (c) 1995-2002 Keil Software.  All rights reserved.
  4 -----------------------------------------------------------------------------*/
  5 #include "stdlib.h"
  6 
  7 /*-----------------------------------------------
  8 Memory pool block structure and typedefs.
  9 Memory is laid out as follows:
 10 
 11 {[NXT|LEN][BLK (LEN bytes)]}{[NXT|LEN][BLK]}...
 12 
 13 Note that the size of a node is:
 14 __mem__.len + sizeof (__mem__)
 15 -----------------------------------------------*/
 16 struct __mem__
 17 {
 18     struct __mem__ _MALLOC_MEM_ *next;    /* single-linked list */
 19     unsigned int                 len;    /* length of following block */
 20 };
 21 
 22 typedef struct __mem__         __memt__;
 23 typedef __memt__ _MALLOC_MEM_ *__memp__;
 24 
 25 #define    HLEN    (sizeof(__memt__))
 26 
 27 /*-----------------------------------------------
 28 Memory pool headers.  AVAIL points to the first
 29 available block or is NULL if there are no free
 30 blocks.  ROVER is a roving header that points to
 31 a block somewhere in the list.
 32 
 33 Note that the list is maintained in address
 34 order.  AVAIL points to the block with the
 35 lowest address.  That block points to the block
 36 with the next higher address and so on.
 37 -----------------------------------------------*/
 38 extern __memt__ _MALLOC_MEM_ __mem_avail__ [];
 39 
 40 #define AVAIL    (__mem_avail__[0])
 41 
 42 /*-----------------------------------------------------------------------------
 43 -----------------------------------------------------------------------------*/
 44 void free (
 45            void _MALLOC_MEM_ *memp)
 46 {
 47     /*-----------------------------------------------
 48     FREE attempts to organize Q, P0, and P so that
 49     Q < P0 < P.  Then, P0 is inserted into the free
 50     list so that the list is maintained in address
 51     order.
 52 
 53     FREE also attempts to consolidate small blocks
 54     into the largest block possible.  So, after
 55     allocating all memory and freeing all memory,
 56     you will have a single block that is the size
 57     of the memory pool.  The overhead for the merge
 58     is very minimal.
 59     -----------------------------------------------*/
 60     __memp__ q;        /* ptr to free block */
 61     __memp__ p;        /* q->next */
 62     __memp__ p0;        /* block to free */
 63 
 64     /*-----------------------------------------------
 65     If the user tried to free NULL, get out now.
 66     Otherwise, get the address of the header of the
 67     memp block (P0).  Then, try to locate Q and P
 68     such that Q < P0 < P.
 69     -----------------------------------------------*/
 70     if ((memp == NULL) || (AVAIL.len == 0))
 71         return;
 72 
 73     p0 = memp;
 74     p0 = &p0 [-1];        /* get address of header */
 75 
 76     /*-----------------------------------------------
 77     Initialize.
 78     Q = Location of first available block.
 79     -----------------------------------------------*/
 80     q = &AVAIL;
 81 
 82     /*-----------------------------------------------
 83     B2. Advance P.
 84     Hop through the list until we find a free block
 85     that is located in memory AFTER the block we're
 86     trying to free.
 87     -----------------------------------------------*/
 88     while (1)
 89     {
 90         p = q->next;
 91 
 92         if ((p == NULL) || (p > memp))
 93             break;
 94 
 95         q = p;
 96     }
 97 
 98     /*-----------------------------------------------
 99     B3. Check upper bound.
100     If P0 and P are contiguous, merge block P into
101     block P0.
102     -----------------------------------------------*/
103     if ((p != NULL) && ((((char _MALLOC_MEM_ *)memp) + p0->len) == p))
104     {
105         p0->len += p->len + HLEN;
106         p0->next = p->next;
107     }
108     else
109     {
110         p0->next = p;
111     }
112 
113     /*-----------------------------------------------
114     B4. Check lower bound.
115     If Q and P0 are contiguous, merge P0 into Q.
116     -----------------------------------------------*/
117     if ((((char _MALLOC_MEM_ *)q) + q->len + HLEN) == p0)
118     {
119         q->len += p0->len + HLEN;
120         q->next = p0->next;
121     }
122     else
123     {
124         q->next = p0;
125     }
126 }
127 \

好,keil 中附带的源码也差不多分析到这里了。下面给出我移植到VS2008的内存管理源码:

  1 #include <iostream>
  2 #include <vector>
  3 using namespace std;
  4 
  5 
  6 static char heap_mem[1024];                        /*    模拟堆内存    */
  7 
  8 #define    HLEN            (sizeof(__memt__))
  9 #define AVAIL            (__mem_avail__[0])        /*    用于代替__mem_avail__[0]的宏定义    */
 10 #define MIN_POOL_SIZE    (HLEN * 10)                
 11 #define MIN_BLOCK        (HLEN * 4)    
 12 
 13 
 14 struct __mem__
 15 {
 16     struct __mem__    *next;    /* 用于连接堆内存中的空闲块 */
 17     unsigned int    len;      /* 下一个内存块的长度 */
 18 };
 19 
 20 typedef struct __mem__         __memt__;
 21 typedef __memt__  *__memp__;
 22 
 23 __memt__  __mem_avail__ [2] =
 24 { 
 25     { NULL, 0 },    /*    用于管理堆内存的头结点,当调用完init_mempool()之后会指向首块内存块,如果堆内存已经用完,则__mem_avail__[0].next为NULL */
 26     { NULL, 0 },    /* UNUSED but necessary so free doesn't join HEAD or ROVER with the pool */
 27                     /*    原文的解释如上    */
 28 };
 29 
 30 
 31 
 32 /*    初始化内存池,    */
 33 int init_mempool (void  *pool, unsigned int size)
 34 {
 35 
 36     if (size < MIN_POOL_SIZE)    /*    如果内存小于定义最小的内存池,则返回内存池初始化失败    */
 37         return (0);     
 38 
 39 
 40     if (pool == NULL)
 41     {
 42         pool = (void *)1;        /*    如果内存池传入的是NULL,则将内存池指向地址为1的位置。    *
 43                                 /*    因为C51单片机的数据段是存储在XDATA, 大小为0x0 ~ 0xffff 共64K可用    */
 44         size--;                    /*    取size-1的内存大小作为堆内存    */
 45         /*    注意的是,若是在X86兼容机不能这么写,因为X86的机子是有内存保护的    */
 46     }
 47 
 48 
 49     AVAIL.next = (__memp__)pool;        /*    指向堆内存    */
 50     AVAIL.len  = size;                    /*    堆内存大小    */
 51 
 52 
 53     (AVAIL.next)->next = NULL;            /*    当前的堆内存还是一块空闲的内存    */
 54     (AVAIL.next)->len  = size - HLEN;    /*    除去管理堆内存的头结点信息,还剩下多少空间可用    */
 55 
 56     return (-1);                           /*    返回成功    */
 57 }
 58 
 59 void * _malloc ( unsigned int size)
 60 {
 61     __memp__ q;                /* 指向的是内存空闲块 */
 62     __memp__ p;                /* q->next */
 63     unsigned int k;            /* 剩下的空闲内存块大小 */
 64 
 65 
 66     q = &AVAIL;
 67 
 68 
 69     /*    在AVATL链表中查找空闲的堆内存    */
 70     while (1)
 71     {
 72         if ((p = q->next) == NULL)
 73         {
 74             return (NULL);                            /* 没有空闲的堆内存,返回NULL */
 75         }
 76 
 77         if (p->len >= size)                            /*    找到一个空闲的内存块,并且这个内存块大于所要分配的大小    */
 78             break;
 79 
 80         q = p;
 81     }
 82 
 83     k = p->len - size;                                /* 申请内存后还剩下多少内存 */
 84 
 85     if (k < MIN_BLOCK)                                /* 如果分配的内存太大,超过了最小堆内存块的定义,则一次性把堆内存给分配完,并把AVAIL结点的next域置NULL,表示没有堆内存可再次分配了 */
 86     {
 87         q->next = p->next;
 88         return (&p[1]);                                /* 返回分配的内存指针 */
 89     }
 90 
 91 
 92     k -= HLEN;                                        /*    多分配一个管理分配内存的节点,所以k要多减去一个节点的字节数    */
 93     p->len = k;                                        /*    p所指的是空闲块    */
 94 
 95     
 96     q = (__memp__) (((char *) (&p [1])) + k);        /*    q指向分配内存的起始地址。注意,这里的q所指向的地址是有加上管理分配内存节点的。    */
 97     q->len = size;                                    /*    分配内存的大小,也就是malloc 参数中size的大小,它不包含管理节点的大小    */
 98 
 99     return (&q[1]);                                    /* 返回分配的内存的指针 */
100 }
101 
102 void _free ( void  *memp)
103 {
104 
105     __memp__ q;                    /* 指向空闲块 */
106     __memp__ p;                    /* q->next */
107     __memp__ p0;                /* 要被释放的指针 */
108 
109     if ((memp == NULL) || (AVAIL.len == 0))
110         return;
111 
112     p0 = (__memp__)memp;
113     p0 = &p0 [-1];    
114 
115 
116     q = &AVAIL;
117 
118 
119 
120     /*    查找一个可以挂载memp的合适的挂载节点    */
121     /*    经过这个算法查找,空闲内存块的开始地址会按升序排序    */
122     while (1)
123     {
124         p = q->next;
125 
126         if ((p == NULL) || (p > memp))
127             break;
128 
129         q = p;
130     }
131 
132 
133     /*    memp之后是否有空闲内存,若有则合并内存块        */
134     if ((p != NULL) && ((((char *)memp) + p0->len) == (char *)p))
135     {
136         p0->len += p->len + HLEN;
137         p0->next = p->next;
138     }
139     else
140     {
141         p0->next = p;
142     }
143 
144     /*    memp之前是否有空闲内存,若有则合并后挂到链表中,若没有则直接挂到链表    */
145     /*    加上 q != &AVAIL是为了不让当q == AVAIL时丢失pool内存池         */
146     if ( ( q != &AVAIL ) && ( ((char  *)q) + q->len + HLEN ) == (char *)p0 )
147     {
148         q->len += p0->len + HLEN;
149         q->next = p0->next;
150     }
151     else
152     {
153         q->next = p0;
154     }
155 }
156 
157 
158 int main (void)
159 {
160   //只可以调用一次。
161     init_mempool(heap_mem, sizeof(heap_mem));
162 
163     int *p = (int *)_malloc (sizeof(int)*10);
164     int *q = (int *)_malloc (sizeof(int));
165     
166 
167     _free (p);
168 
169 
170     
171 
172 
173     return 0;
174 }

  5.回顾总结一下C\C++中内存管理:

    a) 在调用 void* malloc(unsigned int size) 函数进行内存分配的时候,系统(嵌入式的机子裸机跑可能没有上系统) 或者说是C运行库或分配的内容绝不仅仅是size个大小,它还要加上一个管理这个分配的内存块的节点信息,一般放在这个内存块之前(Windows中也是放在分配的内存块之前)。

    b) 调用void free(void *memp) 进行内存释放的时候,其实就是将memp所指内存块挂到管理内存块的链表上去。

    c) 通过例子也可以发现,当多次malloc 和free之后内存碎片的情况将会越来越严重。这也是动态分配内存的一个弱点吧。

    d) 计算机中并没有将内存分为堆或栈,对内存进行分类的仅仅是我们计算机的使用者。

 

posted @ 2013-05-19 22:48  左懒  阅读(2616)  评论(4编辑  收藏  举报