淺析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) 計算機中並沒有將內存分為堆或棧,對內存進行分類的僅僅是我們計算機的使用者。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM