在嵌入式系统中,realloc()函数安全吗?

10

在开发嵌入式系统软件时,我经常使用realloc()函数。现在有人告诉我在嵌入式系统中“不应该使用realloc()”,但没有任何解释。

realloc()在嵌入式系统中是否有危险?为什么?


2
为什么不问告诉你这件事的人呢? - cnicutar
3
嵌入式系统是当前非常广泛的一个领域。 - Šimon Tóth
1
@cnicutar 如果我能问他们,我就不会在这里问了..!! - Jeegar Patel
4
@Mr.32 我的意思是,你不应该无条件接受没有论据支持的观点。 - cnicutar
@cnicutar,你说得对。我曾经和他们争论过,但他们只是说realloc在嵌入式系统中很危险,所以不要使用。我只是一名开发人员,所以必须听从他们的意见..! - Jeegar Patel
特别是 realloc() 而不是 malloc()alloca()?我记得曾经在一本书中看到过对 realloc() 进行批评,因为它的行为高度重载,但我并不完全赞同这种观点,如果有人因此禁止使用它,我会感到失望。 - sh1
6个回答

21

是的,所有动态内存分配都被视为危险,并且被大多数“高完整性”嵌入式系统禁止使用,例如工业/汽车/航空/医疗技术等等。您的问题的答案取决于您正在进行哪种类型的嵌入式系统。

它被禁用于高完整性嵌入式系统的原因不仅在于潜在的内存泄漏,还有许多与这些函数相关的危险未定义/未规定/实现定义行为。

编辑:我还忘记提到堆片段化,这也是一个危险。此外,MISRA-C 还提到“数据不一致、内存耗尽、非确定性行为”作为不能使用它的理由。前两个似乎相当主观,但非确定性行为绝对是这类系统不允许的。

参考文献:

  • MISRA-C:2004 规则20.4:“不应使用动态堆内存分配。”
  • IEC 61508 功能安全,61508-3 附录B(规范)表B1,SIL1:“没有动态对象”,“没有动态变量”。

4
这是唯一正确的答案。在嵌入式系统中,你永远不应该使用堆内存。将所有数据放置在数据段、bss段或者栈上。这样你就有了固定大小的内存占用,并且可以计算出你的内存使用量。这样你就可以确切地知道自己正在使用多少内存,而且永远不会超过限制。使用堆内存是导致嵌入式系统长时间运行后崩溃最常见的原因。 - DipSwitch
4
我理解这个问题特别涉及到realloc(),而不是关于是否使用动态内存分配。 - pmod
5
现在我们拥有具有64兆甚至半个千字节RAM的“嵌入式系统”,“从不”已经不再适用。 - Dietrich Epp
2
@pmod 这个问题是realloc()是否危险。答案是,是的,就像任何形式的动态内存分配一样。 - Lundin
3
如果你在程序启动时调用了malloc,并且从未再次调用它,那么为什么不一开始就静态地分配内存呢? - Lundin
显示剩余7条评论

5
这取决于特定的嵌入式系统。小型嵌入式系统上的动态内存管理本来就很棘手,但是 realloc 并不比 freemalloc (当然,它并不是做这个的)更加复杂。在有些嵌入式系统上,你甚至不会想到首先调用 malloc。在其他嵌入式系统上,你几乎可以把它当作桌面。
如果你的嵌入式系统具有较差的分配器或内存很少,则 realloc 可能会导致碎片化问题。这也是为什么你要避免使用 malloc,因为它会引起同样的问题。
另一个原因是一些嵌入式系统必须具备高可靠性,而 malloc / realloc 可能会返回 NULL。在这些情况下,所有内存都是静态分配的。

我不是一个嵌入式的人,但你似乎在离题讨论关于free和malloc的事情。realloc可能比这更复杂,只是接口很简单。 - Matt Joiner
我想说的是它并不比 malloc 和 free 更复杂,而不是它由它们某种组合而成。如果我措辞不够准确的话,非常抱歉让您不满意。 - Dietrich Epp

4
在许多嵌入式系统中,自定义内存管理器可以提供比malloc/realloc/free更好的语义。例如,有些应用程序可以使用简单的标记和释放分配器。保留指向未分配内存起始位置的指针,通过将指针向上移动来分配内容,并通过将指针移动到它们下方来丢弃它们。如果需要在保留其他分配的内容的同时丢弃某些内容,则这种方法不起作用,但在不需要这样做的情况下,标记和释放分配器比任何其他分配方法都要便宜。在标记和释放分配器不太好用的一些情况下,从堆的开头分配一些内容,从堆的末尾分配另一些内容可能会有所帮助;可以释放从一个端口分配的内容而不影响从另一个端口分配的内容。
另一种方法是,在非多任务或协作多任务系统中,使用内存句柄而不是直接指针有时会很有用。在典型的基于句柄的系统中,有一个所有已分配对象的表,该表在内存顶部构建向下工作,并且对象本身从底部向上分配。内存中每个已分配的对象都包含对引用它的表格插槽的引用(如果活动)或者包含其大小的指示(如果未使用)。每个对象的表格条目将保存对象的大小以及指向内存中对象的指针。可以通过查找空闲表格插槽(因为表格插槽都是固定大小的,所以很容易),将对象的表格插槽地址存储在空闲内存的开头处,将对象本身存储在其后面,并将空闲内存的起始位置更新为刚刚超过该对象。通过将回溯引用替换为长度指示并释放表中的对象,可以释放对象。如果分配失败,则从内存顶部开始重新定位所有活动对象,覆盖任何未使用的对象,并更新对象表以指向它们的新地址。
这种方法的性能是不确定的,但是碎片化不是问题。此外,在某些协作多任务系统中,可能可以在“后台”执行垃圾收集;只要垃圾收集器可以在通过松弛空间所需的时间内完成一次传递,就可以避免长时间等待。此外,可以使用一些相当简单的“世代”逻辑来提高平均情况下的性能,而牺牲最坏情况下的性能。

2
  1. realloc 可能失败,就像 malloc 一样。这就是为什么在嵌入式系统中可能不应该使用它们的原因之一。

  2. reallocmalloc 更糟糕,因为您需要在 realloc 过程中保证旧指针和新指针都有效。换句话说,在原始的 malloc 空间大小的基础上,您需要两倍的内存空间,再加上任何额外的量(假设 realloc 增加了缓冲区的大小)。

  3. 使用 realloc 会非常危险,因为它可能会返回您内存位置的新指针。这意味着:

    • 所有指向旧指针的引用在 realloc 后都必须更正。
    • 对于多线程系统,realloc 必须是原子的。如果您禁用中断以实现此目的,则 realloc 的时间可能足够长,以至于看门狗会导致硬件重置。

更新:我只是想让它更清楚。我并不是说 realloc 比使用 malloc/free 实现 realloc 更糟糕。那也同样糟糕。如果您可以进行单个的 mallocfree,而不需要调整大小,那就稍微好一些,但仍然很危险。


点2和点3是否适用于手动调用malloc()/free() - sharptooth
  1. 是一个很好的观点,realloc因此比malloc更危险,尽管仅使用malloc本身就被认为是太危险了。
- Lundin
@sharptooth - 对的,realloc 在某些情况下只是在执行 malloc/free 操作。因此,我的回答试图解释 realloc 并不神奇,也不危险。除非使用 malloc/free API 实现 realloc,否则可能不清楚哪里存在风险。我只是想直接指出内存开销问题。 - cmcginty

1

在嵌入式系统中,realloc() 的问题与任何其他系统中的问题并无不同,但在内存更为受限、失败后果更难以接受的系统中,其后果可能更加严重。

到目前为止未提及的一个问题是,realloc()(以及任何其他动态内存操作)是非确定性的;也就是说,它的执行时间是可变和不可预测的。许多嵌入式系统也是实时系统,在这样的系统中,非确定性行为是不可接受的。

另一个问题是线程安全性。请检查您的库文档,以了解您的库是否对动态内存分配进行了线程安全处理。通常情况下,如果是这样,您将需要实现互斥体存根,以将其与特定的线程库或RTOS集成。

并非所有的嵌入式系统都是相同的;如果您的嵌入式系统不是实时的(或者所涉及的进程/任务/线程不是实时元素的一部分,并且有大量未使用的内存或虚拟内存功能),那么在大多数情况下,使用realloc()可能是可以接受的,尽管这样做可能不明智。

与其接受“常规智慧”并禁止动态内存,不如了解您的系统要求和动态内存函数的行为,并做出适当的决策。话虽如此,如果您正在构建可重用性和可移植性尽可能广泛的平台和应用程序的代码,则重新分配可能是一个非常糟糕的想法。例如,不要将其隐藏在库中。

还要注意,C++ STL容器类存在相同的问题,即在增加容器容量时动态重新分配和复制数据。


0

如果可能的话,最好避免使用realloc,因为这个操作很耗费资源,特别是放在循环中:例如,如果需要扩展一些已分配的内存,并且当前块后面没有间隙与下一个已分配的块之间 - 这个操作几乎等同于:malloc + memcopy + free。


2
realloc()malloc()/free() 差吗? - sharptooth
更糟糕,因为重新分配内存可能通过使用相邻的内存块进行优化,但这当然并不总是可能的。 - pmod
1
@pmod:单独调用realloc永远不应该比malloc/memcpy/free更差,因为它总是可以只是那个操作,并且可能会避免memcpy - Dietrich Epp
1
抱歉混淆了否定,我的意思是“至少不会更糟,但在统计上甚至更好”,这取决于实现。 - pmod

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接