在为嵌入式系统开发软件时,我realloc()
多次使用函数。现在有人说我“不应该realloc()
在嵌入式中使用”,没有任何解释。
对嵌入式系统很realloc()
危险,为什么?
在为嵌入式系统开发软件时,我realloc()
多次使用函数。现在有人说我“不应该realloc()
在嵌入式中使用”,没有任何解释。
对嵌入式系统很realloc()
危险,为什么?
是的,所有动态内存分配都被认为是危险的,并且大多数“高完整性”嵌入式系统都禁止使用它,例如工业/汽车/航空航天/医疗技术等。您问题的答案取决于什么样的嵌入式系统你在做。
它被高完整性嵌入式系统禁止的原因不仅是潜在的内存泄漏,而且还有许多与这些功能相关的危险的未定义/未指定/impl.defined 行为。
编辑:我也忘了提到堆碎片,这是另一个危险。此外,MISRA-C 还提到了“数据不一致、内存耗尽、非确定性行为”作为不应使用它的原因。前两者似乎相当主观,但非确定性行为在这类系统中绝对是不允许的。
参考:
这取决于特定的嵌入式系统。开始在小型嵌入式系统上进行动态内存管理很棘手,但realloc
并不比 afree
和更复杂malloc
(当然,它不是这样做的)。在某些嵌入式系统上,您malloc
一开始做梦都不会想到调用。在其他嵌入式系统上,您几乎可以假装它是桌面。
如果您的嵌入式系统分配器不佳或 RAM 不多,则realloc
可能会导致碎片问题。这就是为什么你malloc
也要避免,因为它会导致同样的问题。
另一个原因是一些嵌入式系统必须是高可靠性的,并且malloc
/realloc
可以返回NULL
。在这些情况下,所有内存都是静态分配的。
在许多嵌入式系统中,自定义内存管理器可以提供比 malloc/realloc/free 更好的语义。例如,某些应用程序可以使用简单的标记和释放分配器。保持一个指向尚未分配的内存开头的指针,通过向上移动指针来分配事物,并通过将指针移动到它们下方来抛弃它们。如果有必要抛弃一些东西,同时保留在它们之后分配的其他东西,那么这是行不通的,但是在不需要的情况下,标记和释放分配器比任何其他分配方法都便宜。在某些情况下,mark-and-release 分配器不够好,从堆的开头分配一些东西,从堆的末尾分配一些东西可能会有所帮助。
另一种有时在非多任务或协作多任务系统中有用的方法是使用内存句柄而不是直接指针。在典型的基于句柄的系统中,有一个所有已分配对象的表,构建在内存顶部向下工作,对象本身是自下而上分配的。内存中每个分配的对象要么持有对引用它的表槽的引用(如果是活动的),要么是其大小的指示(如果是死的)。每个对象的表条目将保存对象的大小以及指向内存中对象的指针。可以通过简单地找到一个空闲的表槽来分配对象(很容易,因为表槽都是固定大小的),在空闲内存的开头存储对象的表槽的地址,然后存储对象本身,并更新可用内存的开始以指向刚刚超过对象。可以通过用长度指示替换反向引用并释放表中的对象来释放对象。如果分配失败,从内存顶部开始重新定位所有活动对象,覆盖所有死对象,并更新对象表以指向它们的新地址。
这种方法的性能是不确定的,但碎片化不是问题。此外,在某些协作式多任务系统中,可能会在“后台”执行垃圾收集;如果垃圾收集器可以在通过松弛空间所需的时间内完成一次传递,就可以避免长时间的等待。此外,可以使用一些相当简单的“世代”逻辑来提高平均情况下的性能,但会牺牲最坏情况下的性能。
realloc
可以失败,就像malloc
可以一样。这就是为什么您可能不应该在嵌入式系统中使用任何一种的原因之一。
realloc
比malloc
您需要在realloc
. 换句话说,您将需要 2X 的原始内存空间malloc
,再加上任何额外realloc
的数量(假设正在增加缓冲区大小)。
使用realloc
将是非常危险的,因为它可能会返回一个指向您的内存位置的新指针。这表示:
realloc
。realloc
必须是原子的。如果您禁用中断来实现此目的,则realloc
时间可能足够长以导致看门狗进行硬件复位。更新:我只想说清楚。我并不是说这realloc
比realloc
使用malloc
/实现更糟糕free
。那也一样糟糕。如果您可以在不调整大小的情况下执行一个malloc
and free
,它会稍微好一些,但仍然很危险。
嵌入式系统中 realloc() 的问题与任何其他系统没有什么不同,但在内存更受限制的系统中后果可能更严重,并且失败的后果不太可接受。
到目前为止没有提到的一个问题是 realloc() (以及与此相关的任何其他动态内存操作)是不确定的;那就是它的执行时间是可变的和不可预测的。许多嵌入式系统也是实时系统,在这样的系统中,非确定性行为是不可接受的。
另一个问题是线程安全问题。检查您的库的文档以查看您的库对于动态内存分配是否是线程安全的。通常,如果是这样,您将需要实现互斥存根以将其与您的特定线程库或 RTOS 集成。
并非所有嵌入式系统都是相似的。如果您的嵌入式系统不是实时的(或者所讨论的进程/任务/线程不是实时的,并且独立于实时元素),并且您有大量未使用的内存或虚拟内存功能,如果在大多数情况下可能不明智,那么使用 realloc() 可能是可以接受的。
与其接受“传统智慧”而不管动态内存,您应该了解您的系统要求,以及动态内存功能的行为并做出适当的决定。也就是说,如果您正在为尽可能广泛的平台和应用程序构建可重用性和可移植性的代码,那么重新分配可能是一个非常糟糕的主意。例如,不要将其隐藏在库中。
还要注意,当容器容量增加时,C++ STL 容器类也存在同样的问题,它们会动态地重新分配和复制数据。
好吧,如果可能的话最好避免使用 realloc,因为这个操作代价高昂,尤其是被放入循环中:例如,如果一些分配的内存需要扩展并且在当前块和下一个分配块之间没有间隙 - 这个操作几乎等于:malloc + memcopy + free。