Keil C动态内存管理机制分析及改进(转)

本文详细分析了KeilC库中init_mempool、malloc和free函数的实现原理,并针对其中的不足提出了改进方案,旨在帮助KeilC编程人员更有效地进行动态内存管理。

源: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版,将其中与动态存储管理相关的数据结构介绍如下;

#define _MALLOC_MEM_xdata            /*该行在stdlib.h文件中*/
struct __mem__ {
    struct __mem__ _MALLOC_MEM_ *next;    /*单链表*/ unsigned int len; /*下一块长度*/ }; 

  该结构的next指向堆中的下一空闲内存块,len表示该空闲块除去该块首部的struct__mem__结构所占的字节数后,该块实际可用的字节数。由于next是一个指向XDATA区的指针,故在Keil C中应用程序所定义的堆空间应在XDATA段中定义。

  在Keil C中,堆中的所有空闲内存块是用一个单链表来管理的,struct_mere_即为该链表结点的结构,后面定义的宏AVAIL为该链表的首结点,为叙述方便,以下将该链表称为AVAIL链表。   

复制代码
typedef struct __mem__ __memt__;
typedef __memt__ _MALLOC_MEM_ *__memp__;
__memt__ _MALLOC_MEM_ __mem_avail__ [2] = {
    { NULL, 0 },    / * 堆中空闲内存块头结点* / { NULL, 0 }, / * 未使用,但对f ree 函数防止丢失堆空间或误将链表头加入到堆空间,却是必须的* / / * 笔者注:__mem_avail__ [1]的存在,并不能防止堆丢失,见后面free 函数分析* / }; #define AVAIL (__mem_avail__[0]) 
复制代码

  全局数组__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的源代码如下:

复制代码
#define HLEN ( sizeof (__memt__) )
#define MIN_POOL_SIZE ( HLEN * 10)
int init_mempool (
void _MALLOC_MEM_ *pool, unsigned int size) { if (size < MIN_POOL_SIZE) return(0); / * 失败* / if (pool = = NULL) { pool = 1 ; size - - ; } AVAIL.next = pool ; AVAIL.len = size ; (AVAIL.next) -> next = NULL ; (AVAIL.next) -> len = size - HLEN ; return( -1); / * 成功* / } 
复制代码

  在成功执行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块与相邻块合并。(free库函数的部分源代码见本刊网站——编者注)   值得探讨的是最后一段将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)。

<think>嗯,用户遇到了Keil5内存不足的问题,需要解决方案。从用户提供的引用来看,有几个关键信息点:引用1提到了硬件要求(至少2GB内存),引用2和3涉及安装配置,引用4则提到了代码层面的优化方案。用户上次的问题和我的回复都是关于Keil5安装配置的,但这次向了运行时内存管理,说明用户可能已经从安装阶段进入了实际开发阶段。用户特别强调"增加内存限制配置方法",表明ta更关注系统级设置而非代码优化。深入分析内存不足的几种可能场景:1编译时的报错:常见于大型项目,Keil默认堆栈设置较小(0x1000),特别是使用C++或复杂数据结构时容易溢出2链接阶段的内存耗尽:多发生在代码量超过编译器限制的免费版情况,但用户既然能安装完整版应该不是此问题3调试时的运行时错误:设备实际RAM不足或内存分配策略不当注意到引用4提到了"malloc需手动释放",暗示用户可能在嵌入式开发中使用动态内存,这非常危险——因为裸机系统没有内存回收机制。解决方案应该优先考虑静态分配。用户没有说明具体错误提示,所以需要给出多维度方案:-最直接的工程配置调整(编译器/链接器设置)-代码层面改进(避免动态内存)-环境优化(关闭后台程序)-终极硬件升级方案特别要注意的是,嵌入式开发中"增加内存限制"这个概念很特殊——IDE配置调整的是编译器使用的内存,而非目标芯片的物理内存。需要明确区分这两个概念,避免用户误操作。考虑到用户可能是单片机初学者,解释时要避免底层细节:比如链接脚本修改这类高级操作应该放在"进阶方案"里。优先推荐最安全的项目配置调整法(方案一),这种方法不会影响代码逻辑。最后保留"硬件升级"作为客观提醒,因为若用户真在2GB内存电脑上开发ARMCortex-M项目,编译大型工程确实会很吃力。不过从引用3提到的安装流程看,用户应该使用的是Windows系统,而现代Windows系统本身占用就很大。</think> 以下是针对 Keil 5 内存不足问题的 **系统化解决方案**,按优先级排序: --- ### 🔧 一、工程配置优化(关键步骤) 1. **调整编译器堆栈大小** - 打开工程选项:`Project → Options for Target → Target` - 修改以下参数(根据设备内存调整,示例为 STM32F103): ```c IRAM1 Start: 0x20000000 Size: 0x5000 // 增大内部RAM(如从 0x4000 → 0x5000) IROM1 Start: 0x08000000 Size: 0x10000 // 增大Flash(如从 0x8000 → 0x10000) ``` ![Keil5内存配置界面](https://example.com/keil_ram_config.png) - **注意**:Size 值不能超过芯片物理内存上限(参考设备手册) 2. **启用优化选项** `C/C++ → Optimization Level → 选 Level 2 (-O2)` - 减少冗余代码占用,提升内存利用率 - *进阶*:勾选 `Optimize for Time` 缩短编译时间 --- ### 💻 二、代码级优化 1. **规避动态内存分配** 改用静态数组或全局变量(避免 `malloc`): ```c // 弃用动态内存 // int *data = malloc(1024*sizeof(int)); // 改用静态分配 static uint8_t buffer[1024]; // 预先分配固定空间 ``` *⚠️ 引用[4]提醒:返回局部指针易导致内存泄漏,静态分配更安全[^4]* 2. **减少全局变量** - 使用 `static` 限制作用域 - 用 `const` 修饰只读数据存入 Flash(节省RAM): ```c const char LOGO[] = "Embedded System"; // 存储到Flash ``` 3. **启用压缩算法(针对资源文件)** ```c #pragma pack(push, 1) // 1字节对齐压缩结构体 typedef struct { uint8_t id; uint32_t value; } SensorData; // 原占8字节 → 压缩后5字节 #pragma pack(pop) ``` --- ### ⚙️ 三、开发环境调优 1. **关闭后台服务** - 菜单栏:`Edit → Configuration → Text Completion` - 取消勾选 `Dynamic Syntax Checking`(实时语法检查) - 效果:降低IDE内存占用约20% 2. **清理临时文件** - 手动删除工程目录下 `Objects`、`Listings` 文件夹 - 或执行脚本: ```batch del /s /q *.bak *.dep *.crf __iar*.tmp ``` 3. **调整堆大小(Windows系统级)** - 若报错 `"not enough memory to load file"`: - 修改系统环境变量:`_M_IX86_HEAP=8192`(设置IDE使用8GB虚拟内存) --- ### 🚀 四、终极解决方案 | 方法 | 操作步骤 | 适用场景 | |-----------------------|--------------------------------------------------------------------------|-------------------------| | **外扩内存芯片** | 硬件层为MCU添加SRAM/PSRAM,并在Keil中配置外部内存地址 | 资源消耗型项目(如图像处理) | | **升级设备型号** | 更换更大RAM的MCU(如从STM32F103 → STM32H750) | 项目初期设计阶段 | | **启用内存管理单元** | 在启动文件中激活MMU,实现分页管理 | 需实时操作系统的复杂系统 | --- ### ❗关键提醒 1. 检查编译日志中 `Program Size` 数据: ``` Code: 10240 RO-data: 512 RW-data: 2048 ZI-data: 4096 ``` - **RW+ZI > 芯片RAM容量** 即需优化 2. 免费版 MDK 有32KB代码限制(若超限,提示可能类似内存不足) --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值