c++空指针会占用内存吗_阿波罗 STM32F767 开发板资料连载第四十四章 内存管理实验

1)实验平台:alientek 阿波罗 STM32F767 开发板

2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子

d65b5f06542ebd3e5f82377f825611ae.png

第四十四章 内存管理实验

在第十九章,我们学会了使用 STM32F767 驱动外部 SDRAM,以扩展 STM32F767 的内存,

加上 STM32F767 本身自带的 512K 字节内存,我们可供使用的内存还是比较多的。如果我们所

用的内存都是直接定义一个数组来使用,灵活性会比较差,很多时候不能满足实际使用需求。

本章,我们将学习内存管理,实现对内存的动态管理。本章分为如下几个部分:

44.1 内存管理简介

44.2 硬件设计

44.3 软件设计

44.4 下载验证

44.1 内存管理简介

内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如

何高效,快速的分配,并且在适当的时候释放和回收内存资源。内存管理的实现方法有很多种,

他们其实最终都是要实现 2 个函数:malloc 和 free;malloc 函数用于内存申请,free 函数用于

内存释放。

本章,我们介绍一种比较简单的办法来实现:分块式内存管理。下面我们介绍一下该方法

的实现原理,如图 44.1.1 所示:

54d4ec6800512335771a3ced85e68d3d.png

图 44.1.1 分块式内存管理原理

从上图可以看出,分块式内存管理由内存池和内存管理表两部分组成。内存池被等分为 n

块,对应的内存管理表,大小也为 n,内存管理表的每一个项对应内存池的一块内存。

内存管理表的项值代表的意义为:当该项值为 0 的时候,代表对应的内存块未被占用,当

该项值非零的时候,代表该项对应的内存块已经被占用,其数值则代表被连续占用的内存块数。

比如某项值为 10,那么说明包括本项对应的内存块在内,总共分配了 10 个内存块给外部的某

个指针。

内寸分配方向如图所示,是从顶底的分配方向。即首先从最末端开始找空内存。当内存

管理刚初始化的时候,内存表全部清零,表示没有任何内存块被占用。

分配原理

当指针 p 调用 malloc 申请内存的时候,先判断 p 要分配的内存块数(m),然后从第 n 项开

始,向下查找,直到找到 m 块连续的空内存块(即对应内存管理表项为 0),然后将这 m 个内

存管理表项的值都设置为 m(标记被占用),最后,把最后的这个空内存块的地址返回指针 p,

完成一次分配。注意,如果当内存不够的时候(找到最后也没找到连续的 m 块空闲内存),则

返回 NULL 给 p,表示分配失败。

释放原理

当 p 申请的内存用完,需要释放的时候,调用 free 函数实现。free 函数先判断 p 指向的内

存地址所对应的内存块,然后找到对应的内存管理表项目,得到 p 所占用的内存块数目 m(内

存管理表项目的值就是所分配内存块的数目),将这 m 个内存管理表项目的值都清零,标记释

放,完成一次内存释放。

关于分块式内存管理的原理,我们就介绍到这里。

44.2 硬件设计

本章实验功能简介:开机后,显示提示信息,等待外部输入。KEY0 用于申请内存,每次

申请 2K 字节内存。KEY1 用于写数据到申请到的内存里面。KEY2 用于释放内存。KEY_UP

用于切换操作内存区(内部 SRAM 内存/外部 SDRAM 内存/内部 DTCM 内存)。DS0 用于指

示程序运行状态。本章我们还可以通过 USMART 调试,测试内存管理函数。

本实验用到的硬件资源有:

1) 指示灯 DS0

2) 四个按键

3) 串口

4) LCD 模块

5) SDRAM

这些我们都已经介绍过,接下来我们开始软件设计。

44.3 软件设计

打开本章实验工程可以看到,我们新增了 MALLOC 分组,同时在分组中新建了文件

malloc.c 以及头文件 malloc.h。 内存管理相关的函数和定义主要是在这两个文件中。

打开 malloc.c 文件,代码如下:

//内存池(32 字节对齐)__align(32) u8 mem1base[MEM1_MAX_SIZE];//内部 SRAM 内存池__align(32) u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0XC01F4000)));//外部 SDRAM 内存池,前面 2M 给 LTDC 用了(1280*800*2)__align(32) u8 mem3base[MEM3_MAX_SIZE] __attribute__((at(0X20000000)));//内部 DTCM 内存池//内存管理表u32 mem1mapbase[MEM1_ALLOC_TABLE_SIZE];//内部 SRAM 内存池 MAPu32 mem2mapbase[MEM2_ALLOC_TABLE_SIZE]__attribute__((at(0XC01F4000+MEM2_MAX_SIZE)));//外部 SRAM 内存池 MAPu32 mem3mapbase[MEM3_ALLOC_TABLE_SIZE]__attribute__((at(0X20000000+MEM3_MAX_SIZE)));//内部 DTCM 内存池 MAP//内存管理参数const u32 memtblsize[SRAMBANK]={MEM1_ALLOC_TABLE_SIZE,MEM2_ALLOC_TABLE_SIZE,MEM3_ALLOC_TABLE_SIZE}; //内存表大小const u32 memblksize[SRAMBANK]={MEM1_BLOCK_SIZE,MEM2_BLOCK_SIZE,MEM3_BLOCK_SIZE};//内存分块大小const u32 memsize[SRAMBANK]={MEM1_MAX_SIZE,MEM2_MAX_SIZE,MEM3_MAX_SIZE};//内存总大小//内存管理控制器struct _m_mallco_dev mallco_dev={my_mem_init,//内存初始化my_mem_perused,//内存使用率mem1base,mem2base,mem3base,//内存池mem1mapbase,mem2mapbase,mem3mapbase,//内存管理状态表0,0,0,//内存管理未就绪};//复制内存//*des:目的地址//*src:源地址//n:需要复制的内存长度(字节为单位)void mymemcpy(void *des,void *src,u32 n){ u8 *xdes=des;u8 *xsrc=src; while(n--)*xdes++=*xsrc++;}//设置内存//*s:内存首地址//c :要设置的值//count:需要设置的内存大小(字节为单位)void mymemset(void *s,u8 c,u32 count){ u8 *xs = s; while(count--)*xs++=c;}//内存管理初始化//memx:所属内存块void my_mem_init(u8 memx){ mymemset(mallco_dev.memmap[memx],0,memtblsize[memx]*4);//内存状态表数据清零mallco_dev.memrdy[memx]=1;//内存管理初始化 OK}//获取内存使用率//memx:所属内存块//返回值:使用率(扩大了 10 倍,0~1000,代表 0.0%~100.0%)u16 my_mem_perused(u8 memx){ u32 used=0; u32 i; for(i=0;i=0;offset--)//搜索整个内存控制区 {if(!mallco_dev.memmap[memx][offset])cmemb++;//连续空内存块数增加else cmemb=0;//连续内存块清零if(cmemb==nmemb)//找到了连续 nmemb 个空内存块{ for(i=0;i

这里,我们通过内存管理控制器 mallco_dev 结构体(mallco_dev 结构体见 malloc.h),实现对三

个内存池的管理控制。为甚

首先,是内部 SRAM 内存池,定义为:

__align(64) u8 mem1base[MEM1_MAX_SIZE];

然后,是外部 SDRAM 内存池,定义为:

__align(64) u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0XC01F4000)));

最后,是内部 DTCM 内存池,定义为:

__align(64) u8 mem3base[MEM3_MAX_SIZE] __attribute__((at(0X20000000)));

这里之所以要定义成 3 个,是因为这三个内存区域的地址都不一样,STM32F767 内部内存

分为两大块:1,普通内存(地址从:0X2002 0000 开始,共 384KB),这部分内存任何外设都

可以访问。2,DTCM 内存(地址从:0X2000 0000 开始,共 128KB),这部分内存可以被 CPU

和 DMA 等外设访问!!

而外部 SDRAM,地址是从 0XC000 0000 开始的,共 32768KB(32MB),但是,前面 2MB

用做 RGB LCD 屏的显存,不用于内存管理,所以,用于内存管理的外部 SDRAM 内存池首地

址为:0XC01F4000(0XC000 0000+1280*800*2)。

这样总共有 3 部分内存,而内存池必须是连续的内存空间,才可以,这样 3 个内存区域,

就有 3 个内存池,因此,分成了 3 块来管理。

其中,MEM1_MAX_SIZE、MEM2_MAX_SIZE 和 MEM3_MAX_SIZE 为在 malloc.h 里面

定义的内存池大小,外部 SRAM 内存池指定地址为 0XC01F 4000,紧跟 RGB LCD 屏的显存之

后,DTCM 内存池从 0X2000 0000 开始,是从 DTCM 内存的首地址开始的,而内部 SRAM 内

存池的首地址则由编译器自动分配。__align(64)定义内存池为 64 字节对齐,以适应各种不同场

合的需求。

此部分代码的核心函数为:my_mem_malloc 和 my_mem_free,分别用于内存申请和内存释

放。思路就是我们在 43.1 接所介绍的那样分配和释放内存,不过这两个函数只是内部调用,外

部调用我们使用的是 mymalloc 和 myfree 两个函数。其他函数我们就不多介绍了。然后打开

malloc.h,关键代码如下:

#ifndef NULL#define NULL 0#endif//定义三个内存池#define SRAMIN 0//内部内存池#define SRAMEX 1//外部内存池(SDRAM)#define SRAMCCM 2//CCM 内存池(此部分 SRAM 仅仅 CPU 可以访问!!!)#define SRAMBANK 3//定义支持的 SRAM 块数.//mem1 内存参数设定.mem1 完全处于内部 SRAM 里面.#define MEM1_BLOCK_SIZE64//内存块大小为 64 字节#define MEM1_MAX_SIZE160*1024//最大管理内存 160K#define MEM1_ALLOC_TABLE_SIZE MEM1_MAX_SIZE/MEM1_BLOCK_SIZE//内存表大小//mem2 内存参数设定.mem2 的内存池处于外部 SDRAM 里面#define MEM2_BLOCK_SIZE64//内存块大小为 64 字节#define MEM2_MAX_SIZE28912 *1024//最大管理内存 28912K#define MEM2_ALLOC_TABLE_SIZEMEM2_MAX_SIZE/MEM2_BLOCK_SIZE//内存表大小//mem3 内存参数设定.mem3 处于 CCM,用于管理 CCM#define MEM3_BLOCK_SIZE64//内存块大小为 64 字节#define MEM3_MAX_SIZE60 *1024 //最大管理内存 60K#define MEM3_ALLOC_TABLE_SIZEMEM3_MAX_SIZE/MEM3_BLOCK_SIZE//内存表大小//内存管理控制器struct _m_mallco_dev{void (*init)(u8);//初始化u16 (*perused)(u8);//内存使用率u8 *membase[SRAMBANK];//内存池 管理 SRAMBANK 个区域的内存u32 *memmap[SRAMBANK];//内存管理状态表u8 memrdy[SRAMBANK];//内存管理是否就绪};extern struct _m_mallco_dev mallco_dev; //在 mallco.c 里面定义void mymemset(void *s,u8 c,u32 count);//设置内存void mymemcpy(void *des,void *src,u32 n);//复制内存void my_mem_init(u8 memx);//内存管理初始化函数(外/内部调用)u32 my_mem_malloc(u8 memx,u32 size);//内存分配(内部调用)u8 my_mem_free(u8 memx,u32 offset);//内存释放(内部调用)u16 my_mem_perused(u8 memx) ;//获得内存使用率(外/内部调用)//用户调用函数void myfree(u8 memx,void *ptr);//内存释放(外部调用)void *mymalloc(u8 memx,u32 size);//内存分配(外部调用)void *my#endif

这部分代码,定义了很多关键数据,比如内存块大小的定义:MEM1_BLOCK_SIZE、

MEM2_BLOCK_SIZE 和 MEM3_BLOCK_SIZE,都是 64 字节。内存池总大小,内部 SRAM 内

存池大小为 160K,外部 SDRAM 内存池大小为 28912K,内部 DTCM 内存池大小为 120K。

MEM1_ALLOC_TABLE_SIZE、MEM2_ALLOC_TABLE_SIZE 和 MEM3_ALLOC_TABLE_

SIZE,则分别代表内存池 1、2 和 3 的内存管理表大小。

从这里可以看出,如果内存分块越小,那么内存管理表就越大,当分块为 4 字节 1 个块的

时候,内存管理表就和内存池一样大了(管理表的每项都是 u32 类型)。显然是不合适的,我们

这里取 64 字节,比例为 1:16,内存管理表相对就比较小了。

最后来我们看看主函数代码:

int main(void){ u8 paddr[20]; //存放 P Addr:+p 地址的 ASCII 值u16 memused=0;u8 key,i=0,*p=0,*tp=0;u8 sramx=0; //默认为内部 sram  Cache_Enable(); //打开 L1-Cache HAL_Init(); //初始化 HAL 库 Stm32_Clock_Init(432,25,2,9); //设置时钟,216Mhz  delay_init(216); //延时初始化uart_init(115200); //串口初始化 LED_Init(); //初始化 LED KEY_Init(); //初始化按键 SDRAM_Init(); //初始化 SDRAM LCD_Init(); //初始化 LCDusmart_dev.init(108); //初始化 USMART my_mem_init(SRAMIN); //初始化内部内存池my_mem_init(SRAMEX); //初始化外部内存池my_mem_init(SRAMDTCM); //初始化 DTCM 内存池  ……//此处省略部分代码while(1)  {key=KEY_Scan(0);//不支持连按switch(key){case 0://没有按键按下break;case KEY0_PRES: //KEY0 按下p=mymalloc(sramx,2048);//申请 2K 字节if(p!=NULL)sprintf((char*)p,"Memory Malloc Test%03d",i);//向p写入一些内容break;case KEY1_PRES: //KEY1 按下 if(p!=NULL){sprintf((char*)p,"Memory Malloc Test%03d",i);//更新显示内容LCD_ShowString(30,270,200,16,16,p); //显示 P 的内容}break;case KEY2_PRES: //KEY2 按下 myfree(sramx,p);//释放内存p=0; //指向空地址break;case WKUP_PRES: //KEY UP 按下sramx++; if(sramx>2)sramx=0;if(sramx==0)LCD_ShowString(30,170,200,16,16,"SRAMIN ");else if(sramx==1)LCD_ShowString(30,170,200,16,16,"SRAMEX ");else LCD_ShowString(30,170,200,16,16,"SRAMDTCM");break;}if(tp!=p&&p!=NULL){tp=p;sprintf((char*)paddr,"P Addr:0X%08X",(u32)tp);LCD_ShowString(30,250,200,16,16,paddr); //显示 p 的地址if(p)LCD_ShowString(30,270,200,16,16,p);//显示 P 的内容 else LCD_Fill(30,270,239,266,WHITE); //p=0,清除显示}delay_ms(10); i++;if((i%20)==0)//DS0 闪烁. {memused=my_mem_perused(SRAMIN);sprintf((char*)paddr,"%d.%01d%%",memused/10,memused%10);        LCD_ShowString(30+112,190,200,16,16,paddr); //显示内部内存使用率memused=my_mem_perused(SRAMEX);sprintf((char*)paddr,"%d.%01d%%",memused/10,memused%10);LCD_ShowString(30+112,210,200,16,16,paddr); //显示外部内存使用率memused=my_mem_perused(SRAMDTCM);sprintf((char*)paddr,"%d.%01d%%",memused/10,memused%10);LCD_ShowString(30+112,230,200,16,16,paddr); //显示 CCM 内存使用率LED0_Toggle;}} }

该部分代码比较简单,主要是对 mymalloc 和 myfree 的应用。不过这里提醒大家,如果对

一个指针进行多次内存申请,而之前的申请又没释放,那么将造成“内存泄露”,这是内存管理

所不希望发生的,久而久之,可能导致无内存可用的情况!所以,在使用的时候,请大家一定

记得,申请的内存在用完以后,一定要释放。

另外,本章希望利用 USMART 调试内存管理,所以在 USMART 里面添加了 mymalloc 和

myfree 两个函数,用于测试内存分配和内存释放。大家可以通过 USMART 自行测试。

44.4 下载验证

在代码编译成功之后,我们通过下载代码到 ALIENTEK 阿波罗 STM32 开发板上,得到如

图 44.4.1 所示界面:

a5d95e7a0b5bee6880a1a43ad00da89a.png

图 44.4.1 程序运行效果图

可以看到,所有内存的使用率均为 0%,说明还没有任何内存被使用,此时我们按下 KEY0,

就可以看到内部 SRAM 内存被使用 1.2%了,同时看到下面提示了指针 p 所指向的地址(其实

就是被分配到的内存地址)和内容。多按几次 KEY0,可以看到内存使用率持续上升(注意对

比 p 的值,可以发现是递减的,说明是从顶部开始分配内存!),此时如果按下 KEY2,可以发

现内存使用率降低了 1.2%,但是再按 KEY2 将不再降低,说明“内存泄露”了。这就是前面提

到的对一个指针多次申请内存,而之前申请的内存又没释放,导致的“内存泄露”。

按 KEY_UP 按键,可以切换当前操作内存(内部 SRAM 内存/外部 SDRAM 内存/内部 CCM

内存),KEY1 键用于更新 p 的内容,更新后的内容将重新显示在 LCD 模块上面。注意:当使

外部SDRAM内存的时候,我们需要按很多次KEY0(15次),才可以看到内存使用率上升0.1%,

因为按一次只是申请 2K 字节,15 次才申请 30KB,内存总大小为 28912KB,所以,内存使用

率为:30/28912=0.001(0.1%)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值