当前位置:首页 > > 程序喵大人
[导读]在使用C语言开发嵌入式产品的过程中,当使用到malloc函数时候都会有一个争议, “使用动态内存分配安全吗?” ,就连美国军方在safety-critical的嵌入式航空电子设备代码中,也禁止动态内存分配,我们来细细分析下。


在使用C语言开发嵌入式产品的过程中,当使用到malloc函数时候都会有一个争议, “使用动态内存分配安全吗?” ,就连美国军方在safety-critical的嵌入式航空电子设备代码中,也禁止动态内存分配,我们来细细分析下。

C 库函数 - malloc()

函数简介

malloc的全称是memory allocation,中文叫动态内存分配,用于申请一块连续的指定大小的内存块区域以void* 类型返回分配的内存区域地址。

当无法知道内存具体位置的时候,想要绑定真正的内存空间,就需要用到动态的分配内存,且分配的大小就是程序要求的大小。

函数的声明

用来分配所需的内存空间,并返回一个指向它的指针。

//参数  :size -- 内存块的大小,以字节为单位
//返回值:指针 -- 指向已分配大小的内存
//       NULL -- 如果请求失败
void *malloc(size_t size)

介绍一下用法

#include 
#include 
#include 
int main()
{
   char *str;
 
   str = (char *) malloc(15);
   strcpy(str, "hello world");
   printf("String = %s,  Address = %u\n", str, str);

   free(str);
   return(0);
}

编译结果如下,大家自行体会malloc的用法。

malloc的实现机制

当我们了解了malloc的作用应用范围以及用法之后,我们先看看它是怎么实现内存分配的,在此我们需要先了解几个概念。

虚拟内存地址与物理内存地址

为了简单便捷,现代操作系统在汇编程序(或机器语言)层面,处理内存地址时,都是使用虚拟内存地址。这样每个进程可以自己独享一片2N字节的内存,其中N是机器位数。例如在64位CPU和64位OS下,每个进程的虚拟地址空间为2*64 Byte

虚拟地址的作用主要是简化程序的编写及方便操作系统对进程间内存的隔离管理,由于在机器语言层面都是采用虚拟地址,操作系统会将虚拟内存和实际的物理内存进行映射,CPU芯片上叫做存储器管理单元(Memory Management Unit,MMU)的专用硬件,利用存放在主存中的查询表来动态翻译虚拟地址,才能实现对真实内存数据的操作。

页与地址构成

在现代操作系统是以页(Page)为单位。一个内存页是一段固定大小的连续内存地址的总称。

内存地址可以分为页号和页内偏移量。下面以64位机器,4G物理内存,4K页大小为例,虚拟内存地址和物理内存地址的组成如下:


内存地址构成

上面是虚拟内存地址,下面是物理内存地址。由于页大小都是4K,所以页内都是用低12位表示,而剩下的高地址表示页号。

MMU映射单位并不是字节,而是页,这个映射通过查一个常驻内存的数据结构页表来实现。现在计算机具体的内存地址映射比较复杂,为了加快速度会引入一系列缓存和优化。下面给出一个经过简化的内存地址翻译示意图。


运行时堆

在已经映射的内存空间结尾有一个break指针,这个指针下面是映射好的内存,可以访问,上面则是未映射的访问,不能访问。可以通过系统调用sbrk(位移量)确定brk指针的位置,同时返回brk指针的位置,达到申请内存的目。brk(void *addr)系统调用可以直接将brk设置为某个地址,成功返回0,不成功返回-1。而rlimit则是限制进程堆内存容量的指针。

malloc内存分配原理

malloc采用推进brk指针来增加堆的有效区域来申请内存空间分配内存,维护一个内存空闲链表,当申请内存空间时,搜索内存空闲链表,找到适配的空闲内存空间,然后将空间分割成两个内存块,一个变成分配块,一个变成新的空闲块。如果没有搜索到,那么就会用sbrk()才推进brk指针来申请内存空间。

为什么避免使用

这其实要从malloc和free的设计上考虑,通常,它们是基于列表分配器算法将内存池组织到单个链表中的连续位置,使用分配器来管理该链表,实际上就是寻找空闲位置。

但是在极端的safety-critical系统中,malloc常常极其不可预测,在多核系统上进行多线程开发时是个难题,具体有以下几点。

内存有限,多次申请不易管理

嵌入式的内存就只有几十K到几百K,程序在运行时向系统申请内存使用,在使用完毕后,需要显式的释放,不然后果很严重,在多次申请复杂的逻辑开发时,这就要求程序员对动态分配的内存很了解

碎片

在c语言中的malloc进行的动态内存分配和嵌入式系统中使用到堆区的内存分配会产生内存碎片,例如

char  *p;
if(p=char* malloc(0)==NULL){
  printf("NULL\n");
}
else{
  printf("NOT  NULL");
}

实际上最终出现的并不是NULL,而是NOT NULL 这就说明了进行动态内存分配的时候产生了内存碎片

  • 内部碎片的产生

所有的内存分配必须起始于可被 4、或8 或16 整除的地址,或者因为MMU的分页机制的限制,决定内存分配算法仅能把预定大小的内存块分配给客户。

假设在请求一个17字节的内存块时,它可能会获得20字节、24字节等稍大一点的字节,因此由所需的大小需要四舍五入,而产生的多余空间就叫内部碎片。

  • 外部碎片的产生

频繁的分配与回收物理页面会导致大量的、连续且小的页面块夹杂在已分配的页面中间,就会产生外部碎片。

内存泄漏

分配出去的内存在使用之后没有释放掉,没有回收,长此以往,会造成没有足够的内存可以分配。一般表现为运行时间越长,占用的内存越多,最终导致系统奔溃。

所以在进行硬件内存比较小的外围开发的时候,一定要避免内存泄漏,合理的使用内存空间,才能更好的发挥硬件的作用。

怎么解决

在继续使用malloc和free的情况下

正确使用malloc函数分配内存

在实际应用中,我们可以试着把连续的大块内存按分区来管理。每个分区中包含整数个大小相同的内存块。如图所示:

利用这种机制,就可以得到和释放固定大小的内存块。这样内存的申请和释放函数的执行时间就是确定的了,但是特定的内存块在释放时,必须重新回到它原本属于的内存分区。

正确使用free函数释放内存

free函数其实就做了一件事:斩断指针变量和这块内存的对应关系,在使用free(p) 函数内存释放后,指针变量p本身保存的地址并没有改变,那我们必须需重新把p的值变为NULL:即p = NULL

自定义一套内存分配器

尽量避免使用malloc时,我们可以自定义一套本地线程内存分配器,基于栈的分配器,以及基于本地线程的分配器,通过为每个线程分配特定的内存池来避免冲突

最后

在嵌入式系统中,并不是说不使用malloc()和free()管理内存,而是说在使用时需要让我们的代码更具预测性,避免不必要的未知bug产生。


往期推荐




     

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
换一批
延伸阅读

当我们谈起C语言,很多人第一印象是面向底层、面向系统的编译型语言,写出来的程序一般都是从头到尾跑一遍就结束,很少和用户交互。但实际上,C语言从诞生开始就支持交互式的程序设计,通过标准输入输出和用户实时交互,接收用户输入、...

关键字: C语言 编程

在STM32嵌入式开发中,精确延时是非常基础但又极其关键的功能。无论是驱动单总线传感器(比如DS18B20)、控制LCD屏幕时序、还是生成精确的脉冲信号,都需要用到微秒级甚至纳秒级精度的延时。很多新手刚开始使用STM32...

关键字: STM32 嵌入式

在C语言开发中,位操作符是最容易被新手忽略,却能在嵌入式开发、底层驱动、算法优化中发挥巨大作用的工具。和常规的算术操作、逻辑操作相比,位操作直接操作二进制位,执行效率更高,占用代码空间更小,能轻松实现很多用常规方法很难实...

关键字: C语言 位操作符

在C语言开发中,原生字符串的使用一直存在诸多不便。传统C语言中,字符串本质是以'\0'结尾的固定字符数组,开发人员必须提前预估字符串的最大长度:如果预估过小,拼接或插入字符时会出现缓冲区溢出,引发内存越界错误;如果预估过...

关键字: C语言 字符串

随着半导体测试向更高复杂性与并行度演进,多工位自动测试设备(ATE)和SiC/GaN测试对电感、电容和电阻(LCR)测量的需求不断提升。然而,传统的外接台式LCR仪表和基于线缆的设置难以扩展,而且会降低可重复性。本文介绍...

关键字: 半导体 电阻 嵌入式

智能高尔夫球追踪系统是一项创新的嵌入式电子项目,旨在展示如何将紧凑型物联网硬件集成到体育科技应用中。在体育领域,高尔夫球扮演着主要角色,但在现代时代,所有设备都变得更加智能化,高尔夫球也由此演变为智能高尔夫球。本项目结合...

关键字: 嵌入式 物联网 NRF无线技术

在工业自动化、智能传感、嵌入式组网等分布式总线系统中,设备自动地址分配是实现节点互联互通、即插即用的核心技术。传统人工配置地址方式存在操作繁琐、扩展性差、地址冲突风险高、维护成本高等诸多问题,已无法适配大规模、动态化的总...

关键字: 总线 嵌入式 组网

2026年6月8日 – 专注于引入新品的全球电子元器件和工业自动化产品授权代理商贸泽电子 (Mouser Electronics) 正式宣布,首次荣获全球嵌入式应用安全连接解决方案知名供应商NXP® Semiconduc...

关键字: 物联网 移动设备 嵌入式

城市灯火通明、生活井然运转的背后,总有人在不被注意的地方,日复一日地坚持着。他们或许没有惊天动地的故事,却在漫长岁月里,用自己的方式守护着他人的生活。近日,乡村教师班爱花、爱心厨房运营者丫丫妈,以及“扛楼女工”云姐的故事...

关键字: 西门子家电 洗碗机 嵌入式

2026年5月15日,正值“世界无幽日”,一组数据再次引发公众关注:据《中国幽门螺杆菌感染防控》白皮书显示,我国幽门螺杆菌人群感染率已接近50%,涉及超过7亿人口,且家庭内传播特征极为显著——父母若感染,子女感染风险升高...

关键字: 洗碗机 AI 嵌入式
关闭