微信公众号文章《给导弹写代码,为什么严禁使用动态内存分配?》中说:
洛克希德·马丁公司为 F-35 写的编码规范,工控和军工领域的 MISRA C 标准,有一条规则字数很短,但语气重得吓人:
严禁使用malloc、free、new和delete。
MISRA C 是一套面向 C 语言的编码规范,由汽车行业相关组织 MISRA 制定,旨在减少编程缺陷和安全风险,提升软件的可预测性、可移植性和可验证性。它广泛应用于汽车电子、工业控制等安全关键领域。
MISRA C:2012 规则 21.3 禁止使用标准库内存分配函数,包括 malloc()、calloc()、realloc() 和 free();规则 Dir 4.12 则要求避免动态内存分配。后者并非绝对禁止所有动态分配,因此具体项目还需要结合采用的规范版本、规则适用条件和偏离许可进行判断。
另外,new 和 delete 是 C 的动态内存分配与释放运算符,不属于 MISRA C 的直接管辖范围,相关要求应参考适用的 MISRA C 规范。
这些函数为什么受到限制?
malloc()用于在运行时申请指定大小的内存free()用于释放内存calloc()申请内存并将其初始化为零realloc()用于调整已分配内存的大小- C++ 中,
new用于动态创建对象,delete用于销毁对象并释放相应内存。
限制这些操作,并非单纯为了防止内存泄漏,更重要的是控制运行时行为。
首先,动态分配可能失败,也可能因内存反复申请和释放产生碎片,使后续分配更加困难。
其次,通用内存分配器的执行时间可能随堆状态发生变化,难以直接证明其最坏执行时间。对于具有严格截止时间的实时控制系统,这种不确定性会增加时序分析和验证的难度。
动态内存还会引入额外的生命周期管理问题,例如重复释放、悬空指针、内存越界和资源耗尽。这些问题在普通业务系统中可能通过重启或升级缓解,但在部分安全关键系统中,故障恢复成本极高。
需要强调的是,动态内存并不必然导致执行时间超限或系统失效。真正的问题在于:如果无法证明内存分配的容量、失败处理和执行时间满足系统约束,就很难将其纳入严格的安全论证。
不使用动态内存,如何管理数据?工程上通常采用三种替代方案:
- 静态分配。 在编译阶段确定数组或结构体的存储空间。例如,算法最多跟踪 32 个目标,就预先分配 32 个目标的存储空间,并明确处理超出容量的情况。
- 固定内存池。 提前划分固定数量、固定大小的内存块,通过位图或空闲链表管理资源,避免依赖通用堆分配器。不过,仍须验证分配时间、并发安全性和资源耗尽行为。
- 启动阶段预分配。 系统初始化时一次性准备运行所需资源,之后不再申请或释放内存。但如果初始化过程使用了动态分配函数,仍需确认是否符合项目规范及安全要求。
这些方案的共同点,是提前确定资源上限,明确分配失败时的处理策略,并让关键路径的执行时间更容易分析和验证。
归根结底,安全关键软件并非简单追求低内存占用,而是优先保证资源可控、行为可预测和故障可管理。相比运行时的灵活性,能够证明系统在最坏情况下仍满足安全与实时约束,才是这类工程最重要的设计目标。