Keil C的应用动态存储管理的原理和实现方法分析

keil c是常用的嵌入式系统编程工具,它通过init_mempool、mallloe、free等函数,提供了动态存储管理等功能。本文通过对init_mempool、mallloe和free这3个keilc库函数源代码的分析,揭示其实现的原理和方法,并对其中的不足作了改进,以使keil c编程人员更好地应用动态存储管理。
1 相关数据结构、变量及说明
在keil c安装目录下的\c5l\lib目录下,有实现init_mempool、mallloe和free这3个函数的c源文件init_mere.c、malloc.e和free.c。下面针对keil c7.5a版,将其中与动态存储管理相关的数据结构介绍如下:
该结构的next指向堆中的下一空闲内存块,len表示该空闲块除去该块首部的struct__mem__结构所占的字节数后,该块实际可用的字节数。由于next是一个指向xdata区的指针,故在keil c中应用程序所定义的堆空间应在xdata段中定义。
在keil c中,堆中的所有空闲内存块是用一个单链表来管理的,struct_mere_即为该链表结点的结构,后面定义的宏avail为该链表的首结点,为叙述方便,以下将该链表称为avail链表。
#define avail(__mem_avail_[o])
全局数组__mem_ avail_实际也是struct__mem__类型,__mem_avail__[o]的next指向堆中首块空闲块。如果堆中已无空闲内存块,则__mem_avail__[0]的next为null(0值)。为使程序代码简洁,定义了宏avail来代替__mem_avail__[o]。
2 init_mempool函数剖析
函数int_mempool(void_malloc_mem_*pool,unsigned int size)失败时将返回0,成功则返回一1,参数pool指向应用程序定义的堆空间,参数size为堆空间的字节数。如果应用程序提供的堆空间太小(size的值太小),将失去实际意义,故函数将返回0表示失败。当size参数足够大,则会初始化avail(即_mem_avail__[o]),使其next域指向pool参数所指向的堆空间,len域为pool参数所指向的堆空间的总字节数size。其在keilc 7.5a库中init_mem.c的源代码如下:
在成功执行init_mempool函数后,将得到如图1所示的一个数据结构。另外,链首结点avail的len域记录了整个堆的字节数。链首avail结点的next域指向的是首块空闲块,当经过多次的malloe函数而堆中投有空闲内存块时,avail结点的next域将为null值。
很明显,从上面的if(pool==null){pool=1;size--;)这部分源代码来看,如果应用程序中pool参数为空指针(pool为0)时,显然不能直接将avail,的next域的值赋为空指针的(即赋为o)。将pool的值改为1,再将size的值减l,这样,init_mempool函数会在xdata区中,从地址l开始,取size一1个字节作为堆来使用。如果源程序有定义在xdata区的变量,则这些变量所占的存储单元也可能会被当成堆空间的一部分,这无疑是有潜在风险的。
部分程序员在调用init_mempool函数时,习惯将pool参数设为一个形如0xaaaa数字表示的绝对地址,如果不加特别防范,也是不妥的,因为keil c可能会在此方式指定的堆空间中分配临时变量。好的习惯是定义一个字节数组作为堆空间,再将数组名作为pool参数调用init_mempool函数。
在keil c的联机文档中,指明了init_mempool在应用程序中只能被调用一次,那么,如果多次调用该函数又会有什么后果呢?从该函数的源代码来分析,多次调用init_mempoo1函数,会导致重新初始化首结点avail的next域和len域的值,将使avail链表中的原有管理信息丢失,从而导致一些很难诊断的问题。
对此问题,可采用如下保护措施。当发现avail链表中已有管理信息时,则返回失败标志,函数直接返回。具体的方法是检查avail结点的len域,由于其被初始化为零,如果发现其值非零,则表明init_mempool函数已被成功调用过,此时函数直接返回。
3 malloc函数分析
malloc函数的原形是void *malloc(unsigned intsize),size参数为需动态申请的内存块的字节数。
malloc函数的算法是查找avail链表中各结点next指针所指向的空闲内存块。如果某块的空闲字节数≥size参数,则停止查找,并从该块进行内存分配,返回一个指向所分配内存块的指针给应用程序。如果没有找到符合要求的空闲内存块,则返回空指针给应用程序。
需要注意的是,avail链表中除首结点avail外,其余各节点位于堆中各空闲内存块开始处的一个struct__mem__结构中,其len域为该空闲块总字节数减去sizeof(stiuct__mem)后的值,即该块实际空闲的字节数;next域指向堆中下一空闲内存块。
设链表节点p指向所找到的空闲内存块,如果在p空闲块分配size个字节后,剩余的字节数不多,则将p块从avail链表中删除,然后返回一个指向p块偏移sizeof(struct__mem)处的指针。如果在p空闲块分配size个字节后,该块仍剩余较多的字节数,则需对该块进行分割,将多出的这一部分保留在avail链表中。(以下部分有省略,全文请见本刊网站——编者注)
4 free函数分析及改进
free函数的原形是void free(void xdata *memp),参数memp指向所要释放的内存块。
在avail链表中,各结点是按其所指空闲内存块开始地址的大小按升序排列的。free函数的算法是在avail链表中查一个节点p(其前驱为q),当p节点所指空问内存块的地址大于参数memp所指内存块的起始地址时,则将memp块插入到该节点之前,如没有找到这样的节点,则memp块插到链尾。在插入memp块时,还将检查在memp块的前后是否存在地址相邻的空闲内存块,如果有,则将memp块与相邻块合并。
值得探讨的是最后一段将memp块与前一块(q块)合并的这部分代码。如果在执行此部分代码之前,q指向首结点avail,而此时欲将q块与memp块合并,显然是不合理的。实际上,此时应当将q的next指针的值设为memp块的开始地址p0。由于keilc7.5a中,free库函数的源程序中没有考虑这种特殊情况,因此可能会引发严重后果。
由源代码分析可知,q指向首结点avail,而此时如果满足。memp块与q块合并的判定条件,执行q>1en+=p0一>len+hl,en和q一>next=po一>next后,不但不能回收内存,反而导致memp块丢失;同时,avail的len域的值也不正确。如果此时po一>next又为null,则会导致整个堆内存的丢失。
笔者特在keil c7.5 a版中设计了一个示例(见本刊网站),用于引发该错误。要防止这种错误,只需将if((((char_malloc_mem_*)q)+q一>len+hlen)==po)判定语句改为if((q!=&avail)&&(((char_malloc_mem_*)q)+q一>len+hlen)==p0)即可。有兴趣的可通过电子邮件与笔者联系(cqdoml@sina.com)。


微雪电子 树莓派游戏机扩展板|GamePi43 4.3寸屏介绍
特斯拉延长标准续航升级版Model 3在国内的交付时间
减少焊接机器人应力与变形的工艺措施
台积电将在2021年开设新研发中心,致力于研究2纳米芯片
idc是什么意思_idc机房是什么
Keil C的应用动态存储管理的原理和实现方法分析
首批预定的AirPods 2陆续发货 iOS 12.2正式版即将到来
半导体腐蚀新技术
9月份国内手机市场总体出货量3623.6万部,5G手机不超50万部
荣耀将重启Magic系列并定位顶级高端旗舰
KT148A语音芯片按键版本一对一触发播放常见的问题集锦FAQ_V4
研华嵌入式创新平台服务的发展迎来了5G智慧时代
基于工业机器人的生产线系统设计方案
苹果或自建地图内容,不想依赖供应商?
负荷管理终端被控开关原理是什么
腾讯自平衡轮式移动机器人:挑战梅花桩
外部MOSFET可降低基于智能功率选择器的充电器中的I²R损耗
CNC数控插铣加工的介绍和使用
iPhone8什么时候上市?iphone8最新消息:iOS11+全面屏+双摄+无实体按键,灵感也许来自华为?
基于大数据云平台的家用自来水抄表系统