社区编辑申请
注册/登录
C语言动态内存分配
开发 后端
动态内存分配的好处在于需要内存的时候可以按需分配,当不需要内存的时候可以将其释放掉,这样可以高效的利用内存。下面本文从零开始实现一个完整的动态内存分配。

 前言

首先要明白为何需要动态内存分配,熟悉C语言的读者应该对这个比较熟悉,需要一段内存时会使用malloc函数来申请所需要大小的内存,函数返回一段内存的首地址。简单来说,动态内存分配的好处在于需要内存的时候可以按需分配,当不需要内存的时候可以将其释放掉,这样可以高效的利用内存。下面本文从零开始实现一个完整的动态内存分配。

简单动态内存分配实现

内存分配是将没有使用的内存块给需要的变量(普通变量、指针变量、结构体变量等等)使用,由于其使用后需要进行释放,这就会导致空闲的内存是分散在内存池中的。因此,必须要对内存进行管理,也就是对内存的使用情况做标记。

上图是一个内存池使用后的某一时刻,可以看到,使用的块和没有使用的块并不是连续的,这样就需要用一个表对其进行标记,这个表称为BitMap。假设现在将内存按照每个Byte进行划分,然后用一个bit对块进行标记,1表示已使用,0表示没有使用,这样一个块需要一个bit。

下面来用C语言来实现这个简单的动态内存分配。 

  1. #include <stdio.h>  
  2. #define MEM_POOL_SIZE  64  
  3. unsigned char MemPool[MEM_POOL_SIZE];  
  4. unsigned char BitMap[MEM_POOL_SIZE/8]={0};  
  5. //BitMap[0] MSB->LSB  MemPool[0 ~ 8]  
  6. //BitMap[1] MSB->LSB  MemPool[9 ~15]  
  7. //BitMap[2] MSB->LSB  MemPool[16~23]  
  8. // ...  
  9. void InitMemAlloc(void)  
  10.  
  11.     int i=MEM_POOL_SIZE 
  12.     while(i--)  
  13.     {  
  14.         MemPool[i]=0;  
  15.     }  
  16.     i=MEM_POOL_SIZE/8;  
  17.     while(i--)  
  18.     {  
  19.         BitMap[i]=0;  
  20.     }  
  21.  
  22. void *MemAlloc(unsigned int m_size)  
  23.  
  24.     unsigned int i=0,j=0,k=0,index=0,count=0,mapv=0,cache;  
  25.     if(m_size>MEM_POOL_SIZE)  
  26.     {  
  27.         return NULL;  
  28.     }  
  29.     else  
  30.     {  
  31.         for(;i<MEM_POOL_SIZE/8;i++)  
  32.         {  
  33.             mapv=BitMap[i];   //取出高位  
  34.             for(j=0;j<8;j++)  
  35.             {  
  36.                 cache=(mapv&0x80);  
  37.                 if(cache==0)  
  38.                 {  
  39.                     count++;  
  40.                     if(count==m_size) 
  41.                     {  
  42.                         for(;k<m_size;k++)  
  43.                         {  
  44.                             BitMap[(index+k)/8]|=(1<<(7-((index+k)%8)));  
  45.                         }  
  46.                         return &MemPool[index];  
  47.                     }  
  48.                 }  
  49.                 else  
  50.                 {  
  51.                     count=0 
  52.                     iindex=i*8+j+1;  
  53.                 }  
  54.                 mapv<<=1;  
  55.             }  
  56.         }  
  57.         return NULL;  
  58.     }  
  59. }   
  60. void MemFree(void *p,unsigned int m_size)  
  61.  
  62.     unsigned int k=0,index=(((unsigned int)p)-(unsigned int)MemPool);  
  63.     for(;k<m_size;k++)  
  64.     {  
  65.         BitMap[(index+k)/8]&=~(1<<(7-((index+k)%8)));  
  66.     }  
  67.  
  68. void MemPrintBitMap(void)  
  69.  
  70.     unsigned int i,j,value;   
  71.      for(i=0;i<MEM_POOL_SIZE/8;i++)  
  72.     {  
  73.         value=BitMap[i];  
  74.         for(j=0;j<8;j++)  
  75.         {  
  76.             if(value&0x80)  
  77.                 printf("1 ");  
  78.             else  
  79.                 printf("0 ");  
  80.             value<<=1;  
  81.         }  
  82.         printf("\n");  
  83.     }  
  84.  
  85. double MemGetUsedPercent(void)  
  86.  
  87.     unsigned int i,j,value;  
  88.     double ret=0.0;  
  89.     for(i=0;i<MEM_POOL_SIZE/8;i++)  
  90.     {  
  91.         value=BitMap[i];  
  92.         for(j=0;j<8;j++)  
  93.         {  
  94.             if(value&0x80)  
  95.                 ret++;  
  96.             value<<=1;  
  97.         }  
  98.     }  
  99.     return (ret*100)/MEM_POOL_SIZE; 
  100.  
  101. int main(int argc, char **argv)  
  102.  
  103.     int *p=MemAlloc(10);  
  104.     printf("The pool is used=%f\n",MemGetUsedPercent());  
  105.     MemPrintBitMap();  
  106.     int *q=MemAlloc(5);  
  107.     printf("The pool is used=%f\n",MemGetUsedPercent());  
  108.     MemPrintBitMap(); 
  109.     MemFree(p,5);  
  110.     printf("The pool is used=%f\n",MemGetUsedPercent());  
  111.     MemPrintBitMap();  
  112.     return 0;  

最终终端输出结果如下:

上面已经实现了一个简单的动态内存分配,可以完成内存的分配和释放以及输出使用率和查看位图。这种方式实现的动态内存分配不会产生内部碎片,这也是其优势所在,但其缺点很明显就是利用率太低。

实用的动态内存分配

细心的读者可能已经发现上面的简单动态内存分配有一个缺点,就是一个bit只能表示一个字节,也就是说表示8个字节就需要一个字节的位图,这种映射导致其内存的

这对于很多情况是比较浪费的。为了提高利用率,就必须将映射块的粒度增大,也就是一个Bit的映射范围对应多个字节。

上图给出了一个bit映射到64Byte,这样:

虽然利用率变高了,但是其会产生内部碎片,所谓内部碎片就是在最小粒度内无法使用的内存空间,为何这个空间无法使用了,原因在于当在申请内存块的时候,其内存只能以64B对齐的,即使小于64B,也得按64B来看作,因为这个粒度已经被bitmap标记使用了,当下次使用时,其无法被分配。因此,可以看到,粒度越大,其可能产生的内部内存碎片越大,内存利用率和碎片是需要权衡了,好的算法只能解决外部碎片问题,无法解决内部碎片问题,因此在实现动态内存分配时必须权衡考虑,以达到最优结果。 

 

责任编辑:庞桂玉 来源: C语言与C++编程
相关推荐

2022-04-12 11:38:06

C语言全局变量

2022-04-14 10:10:59

Nginx开源Linux

2022-04-20 20:28:40

HDF 驱动框架鸿蒙操作系统

2021-12-16 06:52:33

2022-05-03 23:44:21

Python动态链接库Ctypes

2022-05-10 16:04:40

编程语言PythonC语言

2022-03-16 10:14:55

C语言C++

2022-05-14 08:05:18

Linux内存管理

2022-03-29 08:30:47

2022-04-01 15:18:04

HarmonyHDF 驱动鸿蒙

2022-04-22 18:48:46

LinuxLinux 内核C 语言

2022-02-25 14:13:28

LinuxC语言开发

2022-04-21 09:26:41

FastDFS开源分布式文件系统

2021-12-30 11:26:31

2022-04-26 08:00:00

存储UFSeMMC

2022-03-08 11:17:54

函数指针回调函数C语言

2022-04-27 09:48:56

JS前端开发

2022-04-12 11:20:11

C 语言Linux编程

2022-04-28 09:46:20

Nginx文件Linux

2022-04-26 09:23:07

Hare编程语言C

同话题下的热门内容

Python 字符串总结,建议收藏!这份Java日志格式规范,拿走不谢!Mybatis-Plus官方发布分库分表神器,一个依赖轻松搞定!后端思维篇:如何应用设计模式优化代码改变 Python 对象规则的黑魔法 Metaclass几种限流算法的Go语言实现JMeter关联之正则表达式提取器在 Go 中实现一个支持并发的 TCP 服务端

编辑推荐

使用Kotlin做开发一个月后的感想面试官问你什么是消息队列?把这篇甩给他!五大自动化测试的Python框架图文详解两种算法:深度优先遍历(DFS)和广度优先遍历(BFS)2018年最流行的十大编程语言,其中包括你用的语言吗?
我收藏的内容
点赞
收藏

51CTO技术栈公众号