嵌入式C,使用动态内存分配安全吗?

在嵌入式C语言程序开发中,动态内存分配如使用不当,可能导致系统安全性问题。美国军方按照DO-178B标准,禁止在safety-critical的嵌入式航空电子设备代码中使用动态内存分配。自定义内存分配器,如基于栈或本地线程的分配器,能提供更安全、更可预测的内存管理。

 

想在C语言程序员之间开始一个激烈的,或者说有争议的讨论很简单,只需要问:“使用动态内存分配安全吗?”
使用动态内存分配安全吗?

在C语言程序开发中,动态内存分配允许程序在运行时向系统申请内存使用,只不过在使用完毕后,需要显式的释放之,这就要求程序员对动态分配的内存了然于胸。

在非常重视安全(safety-critical)的嵌入式C语言程序开发中,动态内存分配广泛被认为是禁忌。使用C语言的malloc()和free()库函数可能会带来灾难性的副作用,例如内存泄漏或者碎片。此外,malloc()常常会表现出极其不可预测的特性,这使其成为在多核系统上进行多线程C语言程序开发的瓶颈。

事实上,由于malloc()存在安全风险,美国军方按照DO-178B标准,在safety-critical的嵌入式航空电子设备代码中禁止动态内存分配。

禁止动态内存分配

嵌入式行业的C语言程序员似乎对这个话题有着发自内心的反应。在最近的一次互联网技术小组讨论中,当提到问题:“在嵌入式C语言程序设计种是否使用动态内存分配?”时,77条回复称“使用动态内存分配是对系统容错性的最大危害之一”,还有5条回复称“如果希望系统正常运行时间能够达到‘5个9’(即99.999%),答案就是‘永远不会’使用动态内存分配”。

甚至有相关部门在招聘嵌入式C语言程序员时,会问求职者是否会使用动态内存分配,如果他们使用,就不会被雇用了。

为了通过相关的工作面试,也为了提升C语言代码安全,更好的办法是自定义一套内存分配器,一般分为两种:基于栈的分配器,以及基于本地线程的分配器。写出更安全稳定的C语言代码,就不该再使用标准库提供的malloc()和free()函数了。

为什么美国军方认为C语言标准库函数提供的动态内存分配管理函数malloc()和free()是个糟糕的选择呢?这其实要从malloc()和free()的设计上考虑,通常,它们是基于列表分配器算法的,该算法将内存池组织到单个链表中的连续位置,分配器管理该链表,每次分配实际上就是寻找空闲位置。这种分配器在各种情况下都能相当好的分配和释放内存,但是在极端的safety-critical系统中。

基于栈的内存管理器

在C语言程序开发中,某些应用程序可能只需要申请一些短期对象,很快就会使用完并释放。基于栈(此“栈”不同于函数的调用栈)的内存分配器此时就能大派用场了,该分配器每次分配都返回栈指针当前位置的地址,并按照需求推进指针,如下图:

当该内存被使用完毕,需要被释放时,只需要将栈指针往后返回即可。这样一来,处理内存分配的开销就减少了,因为没有需要管理的指针链表了,也没有需要跟踪的分配内存大小,以及空闲内存位置。另外,由于C语言程序不再跟踪特定的分配内存,所以这种内存分配器也更加安全:不匹配的内存释放不会导致内存泄漏。

对于C语言标准库提供的内存分配器来说,当内存以随机顺序释放时,列表分配器通常需要向它的链中添加指针和内存长度(这称为碎片)。当程序继续运行时,列表分配器的开销会增加,因为需要管理的元数据数量增加了,寻找合适的空闲内存位置也会更加耗时。而基于堆栈的内存分配器分配的所有内存块都将返回到堆中,碎片化就被避免了。

当在多核处理器平台进行多线程编程时,默认由互斥体做同步控制的malloc()和free()函数就比较难用了。因为他们可能会导致锁冲突,操作系统要解决这些冲突,只能通过损耗性能的上下文切换。

多核处理器多线程编程的挑战

针对此情况,C语言程序员可以自定义本地线程内存分配器,通过为每个线程分配特定的内存池来避免冲突。每个线程的内存分配是在不干扰其他线程的情况下进行的,从而提高了系统性能和程序的可预测性。

当本地线程分配器耗尽内存时,如果系统安全和允许,其他分配器可以再为它分配一个新的内存块。本地线程内存分配器可使用一个列表管理属于自己线程的内存,因此由同一个线程分配和释放的内存不需要协调,不会发生锁冲突导致的性能损耗。

简而言之,不使用malloc()和free()管理内存,使用更具预测性,更安全的自定义内存分配器,可以避免safety-critical代码出现内存安全问题。

### 动态内存分配嵌入式C代码迁移到MinGW环境下的处理方法 在将嵌入式C代码迁移到MinGW环境时,动态内存分配的处理是一个关键问题。以下是对该问题的专业解析和解决方案: #### 1. 嵌入式系统与MinGW环境的内存管理差异 嵌入式系统通常具有有限的内存资源,并且可能不支持标准的动态内存分配函数(如 `malloc` 和 `free`)。相比之下,MinGW编译器默认支持完整的动态内存管理功能[^3]。因此,在迁移过程中需要特别注意以下几点: - 如果嵌入式代码中未使用动态内存分配,则可以直接移植到MinGW环境中。 - 如果嵌入式代码依赖于动态内存分配,则需要评估是否可以在MinGW环境中直接使用标准库函数。 #### 2. 替代方案:静态内存分配 为了减少运行时开销并避免潜在的内存泄漏问题,可以将动态内存分配替换为静态内存分配。以下是实现方法: - 使用全局变量或函数作用域内的静态变量替代动态分配的内存。 - 示例代码如下: ```c // 原始代码:动态内存分配 void example_function() { int *data = (int *)malloc(sizeof(int) * 10); if (data == NULL) { // 处理内存分配失败 } // 使用 data free(data); } // 替换为静态内存分配 void example_function_static() { static int data[10]; // 直接使用 data } ``` #### 3. 使用自定义内存池 如果必须保留动态内存分配的功能,可以实现一个简单的内存池来模拟嵌入式环境中的内存管理机制。这种方法不仅可以控制内存使用量,还可以避免标准库函数带来的额外开销[^3]。 - 示例代码如下: ```c #include <stdint.h> #include <stdio.h> #define MEMORY_POOL_SIZE 100 static uint8_t memory_pool[MEMORY_POOL_SIZE]; static uint8_t *memory_pool_ptr = memory_pool; void *custom_malloc(size_t size) { if ((memory_pool + MEMORY_POOL_SIZE - memory_pool_ptr) < size) { return NULL; // 内存不足 } void *ptr = memory_pool_ptr; memory_pool_ptr += size; return ptr; } void custom_free(void *ptr) { // 简单内存池无需释放 (void)ptr; } int main() { int *data = (int *)custom_malloc(sizeof(int) * 10); if (data != NULL) { printf("Memory allocated successfully.\n"); } else { printf("Memory allocation failed.\n"); } custom_free(data); return 0; } ``` #### 4. 禁用动态内存分配 如果目标是尽可能接近嵌入式环境的行为,可以通过编译选项禁用动态内存分配。例如,使用 `-fno-builtin-malloc` 和 `-fno-builtin-free` 编译选项可以禁用标准库中的 `malloc` 和 `free` 函数[^3]。 #### 5. 调试与优化 在MinGW环境下调试嵌入式代码时,建议使用工具(如 Valgrind)检测内存泄漏和非法访问问题。此外,可以通过以下方式优化内存使用: - 尽量减少全局变量的使用。 - 避免递归调用以减少栈空间消耗。 - 使用 `-Os` 编译选项优化代码大小。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值