Keil的動態內存管理實現——mallo和free函數


     在使用51單片機的時候,由於內存有限,大部分時候都不會使用到動態內存管理函數。而且對於內存管理概念比較模糊的情況下,也不建議在C51中使用malloc和free函數。但在需要使用鏈表的場景中,或者比較復雜的場景中,使用動態內存管理,則可以靈活,同時有效的降低內存使用。

  

  使用51單片機keil自帶的內存管理函數需要包含頭文件STDLIB.H

  Keil自帶的內存管理函數包括如下幾個函數:

extern void init_mempool          (void _MALLOC_MEM_ *p, size_t size);

extern void _MALLOC_MEM_ *malloc  (size_t size);

extern void free                  (void _MALLOC_MEM_ *p);

extern void _MALLOC_MEM_ *realloc (void _MALLOC_MEM_ *p, size_t size);

extern void _MALLOC_MEM_ *calloc  (size_t nmemb, size_t size);

  這些函數keil提供了源碼,位置在:

C:\Keil\C51\LIB,如果是keil5和MDK同時安裝的話,則在位置:\Keil_v5\C51\LIB。

 

 

 

 一,概述

    整體思路為在XDATA區設置一個大的內存池作為heap(堆),使用鏈表的方式把所有的未分配區域串起來,其中頭指針獨立於內存池之外,其他鏈表指針則存在於內存池中。

其中malloc和free是最常用的函數,init_mempool則必須且只能在初始化的時候調用一次,否則整個內存函數會丟失混亂。

  鏈表的類型如下:

struct __mem__

  {

  struct __mem__ _MALLOC_MEM_ *next;    /* single-linked list */

  unsigned int                len;      /* length of following block */

  };

  內存管理模塊首先定義了兩個變量作為頭指針

__memt__ _MALLOC_MEM_ __mem_avail__ [2] =

  {

    { NULL, 0 }, /* 指向可用塊的頭*/

    { NULL, 0 }, /*不使用,為防止free函數把頭指針釋放掉*/

  };

#define AVAIL (__mem_avail__[0])

  AVAIL 指向內存池第一塊可用內存塊,每次分配內存的時候,都從這個頭指針開始查找。對於內存的具體操作, 下面按函數分別分析。

二、具體實現

1,內存池初始化

void init_mempool          (void _MALLOC_MEM_ *p, size_t size);

  函數入口參數p為內存池的指針,內存池需要在xdata區分配,size為內存池的大小。調用 init_mempool初始化內存池的時候,首先會判斷內存首地址是否為0,為0則調過這個字節。因為51單片機的內部RAM和外部RAM是獨立編址的,內存池如果從X:0000H地址開始,則會使鏈表頭指針變成0,成為無效指針。然后把頭指針指向內存池,並把內存池中的唯一一塊內存指針設置為NULL,初始化其大小。

void init_mempool (

void _MALLOC_MEM_ *pool,

unsigned int size)

{

/*-----------------------------------------------

如果內存指向0地址,則指向1並且把內存池大小-1

-----------------------------------------------*/

  if (pool == NULL)   {

    pool = 1;

    size--;

  }

/*-----------------------------------------------

AVAIL頭指向內存池的開始,並且設置內存大小

-----------------------------------------------*/

  AVAIL.next = pool;

  AVAIL.len  = size;

/*-----------------------------------------------

把內存池中的鏈接的塊設置為NULL(因為這是唯一的塊),並初始化 數據區大小

-----------------------------------------------*/

  (AVAIL.next)->next = NULL;

  (AVAIL.next)->len  = size - HLEN; 

}

一個空的內存池在初始化之后,會呈現如下的圖像:

 

 

2,內存申請

    調用malloc分配內存的時候,根據頭指針,找到第一塊空內存,如果空間大小夠則分配成功,返回內存指針,並把剩余內存區域重新配置成未使用區域;如果長度不夠,則繼續尋找下一塊內存,直到找到長度夠的內存,或者最終找不到足夠的內存區域,返回失敗。

    在分配過程中,分割長度超過申請長度的區域時,分配區會放到后部,把前部留出來,僅設置空閑塊的大小;分配區則重新創建一個鏈表頭,記錄分配區的大小。

void _MALLOC_MEM_ *malloc (unsigned int size)

{

__memp__ q; /* 指向空閑區的指針 */

__memp__ p; /* q->next */

unsigned int k; /* 分配區的剩余長度 */

/*-----------------------------------------------

初始化:Q為下一個可用塊的指針

----------------------------------------------*/

q = &AVAIL;

/*----------------------------------------------

鏈表結束: P指向下一塊內存,如果塊無效,則到了最后一鏈

-----------------------------------------------*/

while (1)

  {

  if ((p = q->next) == NULL)

    {

    return (NULL); /* 分配失敗*/

    }

/*-----------------------------------------------

尋找空閑塊:如果內存塊足夠大,預設它,否則復制PQ,然后嘗試下一個空閑塊

-----------------------------------------------*/

  if (p->len >= size)

    break;

  q = p;

  }

/*-----------------------------------------------

預設P至少使用部分P塊滿足分配要求這時按以下設置指針:

    P指向我們要分配的塊,Q->next指向P

-----------------------------------------------*/

k = p->len - size; /* calc. remaining bytes in block */

if (k < MIN_BLOCK) /* rem. bytes too small for new block */

  {

  q->next = p->next;

  return (&p[1]); /* SUCCESS */

  }

/*-----------------------------------------------

分割P塊:如果比我們需要的大,我就把P分割為2塊,剩余空間和分配空間,這意味着,我們需要在分配空間重新創建一個頭

-----------------------------------------------*/

k -= HLEN;

p->len = k;  

//這里把指針當數組用,相當於指針移動一個內存指針長度,再加上K,則讓出了空閑區域

q = (__memp__ ) (((char _MALLOC_MEM_ *) (&p [1])) + k);

q->len = size;

return (&q[1]); /* SUCCESS */

}

 

    在新的內存池分配一次和多次后的內存圖像如下:

 

 

3,內存釋放

  調用free函數釋放已申請內存時,首先從頭查找空閑塊,直到查找到我們需要的內存塊后面的一塊,則退出查找。如果找到后面的空閑塊,則檢查要釋放塊的上下邊界,如果和前一塊空閑區連續,則把要釋放塊合並進前面的空閑塊;如果和后面空閑塊連續,則把后面的空閑塊合並到前面。

     void free (void _MALLOC_MEM_ *memp) 

{

/*-----------------------------------------------

FREE 嘗試組織 Q、P0 和 P,使 Q < P0 < P。然后,P0 插入到空閑列表,那么整個列表按地址排列

FREE 還嘗試合並小塊進入大塊。 所以,分配所有內存並釋放所有內存,我們得到一個跟內存池一樣大的塊(也就是初始化時那樣) 合並的開銷非常小。

-----------------------------------------------*/

__memp__ q; /* ptr to free block */

__memp__ p; /* q->next */

__memp__ p0; /* block to free */ 

/*-----------------------------------------------

如果使用者試圖釋放NULL,直接返回。

否則,獲取內存頭指針P0,然后嘗試定位QP,使 Q < P0 < P

-----------------------------------------------*/

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

  return;

p0 = memp;

p0 = &p0 [-1]; /* get address of header */

/*-----------------------------------------------

初始化:Q = 第一個可用塊.

-----------------------------------------------*/ 

q = &AVAIL;

/*-----------------------------------------------

B2. 前進P

遍歷列表,直到我們要釋放的塊后面的一塊內存,找到一個離釋放塊挨着的空閑塊

----------------------------------------------*/

while (1)

  {

  p = q->next;

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

    break;

  q = p;

  }

/*-----------------------------------------------

B3. 檢查上邊界

如果 P0 和 P 是連續的,則將塊P合並到P0。

-----------------------------------------------*/

if ((p != NULL) && ((((char _MALLOC_MEM_ *)memp) + p0->len) == p))

  {

  p0->len += p->len + HLEN;

  p0->next = p->next;

  }

else

  {

  p0->next = p;

  }

/*-----------------------------------------------

B4. 檢查下邊界

If Q and P0 are contiguous, merge P0 into Q.如果QP0是連續的,則合並P0Q

-----------------------------------------------*/

if ((((char _MALLOC_MEM_ *)q) + q->len + HLEN) == p0)

  {

  q->len += p0->len + HLEN;

  q->next = p0->next;

  }

else

  {

  q->next = p0;

  }

}

    多次申請內存后,再釋放內存,可能的內存圖像如下:

 

 

 


免責聲明!

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



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