使用堆内存(malloc / new)是否会创建不确定的程序?


76

几个月前,我开始用C语言开发用于实时系统的软件,用于空间应用程序以及具有C ++的微控制器。在这样的系统中有一个经验法则:永远不要创建堆对象(因此不要创建malloc / new),因为它会使程序变得不确定。当人们告诉我时,我无法验证这句话的正确性。那么,这是正确的说法吗?

对我来说,困惑是,据我所知,确定性意味着两次运行程序将导致精确,相同的执行路径。据我了解,这是多线程系统的一个问题,因为多次运行相同的程序可能会使不同的线程每次以不同的顺序运行。


12
动态内存分配和取消分配受到内存碎片问题的困扰,这在实时应用程序中是无法预期的。
Gaurav Pathak

22
基本上是这样的:PC程序员被教会使用malloc / new。它们进入了毫无意义的嵌入式系统,因为它们是采用完全不同的体系结构和思维方式构建的。PC程序员不高兴,这不是他们在学校里以为我的想法!而且我在PC上使用堆已有很长时间了!PC程序员不理会所有人,而是继续进行堆分配。该程序原来是胡扯,充满了错误和性能不佳。PC程序员被解雇了。嵌入式系统程序员可以接管一切。从头开始重新编写程序。
伦丁

11
@ PeterA.Schneider作为在粒子和原子物理学领域获得博士学位的物理学家,我已经很难在计算机中确定性与非确定性之间划清界限。这是因为我们可以说生活在一个不确定的世界中。我知道,我的问题很容易引发循环论证,即在某些假设下,在确定性之前是否有任何确定性。但是,这里对确定性的语言使用仅限于计算机科学家如何使用它,实际上与自然中的真正随机性无关。
量子物理学家

8
@ PeterA.Schneider在几乎所有现代计算机中执行不确定性的事情都是微不足道的。一种简单的方法-在等待磁盘读取完成的例程中,记下处理器指令计数器的最低有效位。这受圆盘旋转速度中的剪切湍流影响。您可以对网络数据包执行类似的操作,这受石英晶体中微观区域温度变化的影响,这些变化会影响网络接口时钟和CPU时钟之间的偏斜。您可以从音频输入中提取热噪声。还有许多其他方法。
David Schwartz

5
@ PeterA.Schneider:如今,大多数x86 CPU(自IvyBridge以来都是Intel)都rdrand内置了一条指令,您可以从普通的用户空间进程中执行该指令。它提供了由AES调节的热噪声发生器的真正硬件随机性(除非NSA削弱了设计...)。当然,rdtsc正如David指出的那样,它也是不确定性的,尤其是仅考虑单个过程时,但是在不同时钟域之间进行同步的优点是可以带来一些真正的不确定性。
彼得·科德斯

Answers:


71

在实时系统的环境中,确定性比可重复的“执行路径”更多。另一个必需的属性是关键事件的时间是有限制的。在硬实时系统中,在其允许的时间间隔之外(该间隔开始之前或结束之后)发生的事件表示系统故障。

在这种情况下,使用动态内存分配可能会导致不确定性,尤其是在程序具有变化的分配,取消分配和重新分配模式的情况下。分配,重新分配和重新分配的时间可能会随时间而变化-因此,使整个系统的时间变得不可预测。


28
请注意,不可预测的时间不会使系统成为非实时的。如果分配需要rand()毫秒,并且时间限制大于RAND_MAX,则系统是实时的。
MSalters

4
@MSalters-是的。实时系统要求时序是可预测的边界,而不是每个时序都可以预先预测。
彼得

1
拜托:那你为什么称它为“(非)确定性”?这只是一个没有明确定义的最坏情况执行时间的操作。仅当分配受到例如与“外部”交互的其他进程/应用程序部分(例如,等待某些人按下按钮)的影响时,才变为“不确定”。
Daniel Jour

如果它是一个真正的封闭系统,则可以说它是确定性的,但它仍然是混乱的,即无法预测。话虽如此,我怀疑这是一个真正封闭的系统,因为那将毫无用处。
约翰·吴

@DanielJour-实时系统的定义涉及事件的确定性计时(例如,可以通过分析系统属性来确定事件B将在事件A之后的x到y毫秒之间发生)。如果触发或响应事件的过程没有定义的最坏情况执行时间,则系统将无法满足其实时要求。在软实时系统中,这在一定程度上是可以接受的。在硬实时系统中,事实并非如此。
彼得

40

如上所述,该评论不正确。

使用具有不确定行为的堆管理器会创建具有不确定行为的程序。但这是显而易见的。

不太明显的是存在具有确定性行为的堆管理器。也许最著名的示例是池分配器。它具有N * M字节的数组和available[]N位的掩码。为了进行分配,它检查第一个可用条目(位测试,O(N),确定性上限)。要取消分配,它将设置可用位(O(1))。malloc(X)会将X舍入到M的下一个最大值选择正确的池。

这可能不是很有效,特别是如果您对N和M的选择太高的话。如果选择得太低,程序可能会失败。但是N和M的限制可能会低于没有动态内存分配的等效程序的限制。


循环缓冲区是此分配器的变体,甚至可以在执行开始时使用单个malloc对其进行分配,然后根据上下文进行其他更改-如果您事先知道需要N个不同的缓冲区大小,则可以构建一个分配器,效率更高。
Rsf

1
为池收集器使用位掩码的另一种方法是使用链表,该链表具有O(1)分配和释放。只要代码从不尝试分配一定数量的缓冲区(而不是池中存在的缓冲区),但是,除了缓存问题之外,计时将是完全确定的。
超级猫

1
更一般的情况是在需要确定性行为之前先进行分配。就像这个池分配器需要的内存一样:)
Hans Passant

我的任务是打破嵌入式工程师之间似乎普遍存在的误解,即任意大小的内存分配(malloc()而不是固定大小的块)本质上是非时间确定性的,或者可能导致无限制的碎片化。如图所示,例如,在“硬实时系统中的定时可预测内存分配”中[Herter 2014],存在可预测和高效的算法,但很少提及。我在这里为参与过的实时嵌入式系统实现了约500行代码中的一个:github.com/pavel-kirienko/o1heap
Pavel Kirienko

21

C11标准或n1570中的任何内容都没有说malloc是确定性的(或不是)。在Linux上也没有其他文件,例如malloc(3)。顺便说一句,许多malloc实现都是免费软件

但是malloc会(并且确实)会失败,并且它的性能未知(malloc在我的桌面上进行一次典型的调用实际上只需要不到一微秒的时间,但是我可以想象到一个奇怪的情况,在负载非常大的情况下可能要花费更多的时间,可能要花费几毫秒的时间)电脑;阅读有关rash动的内容)。而且我的Linux桌面具有ASLR(地址空间布局随机化),因此两次运行同一程序会给出不同的malloc-ed地址(在进程的虚拟地址空间中)。顺便说一句,这里是确定性的(在您需要详细说明的特定假设下),但是实际上没有用malloc

确定性意味着两次运行程序将导致精确,相同的执行路径

在大多数嵌入式系统中,这实际上是错误的,因为物理环境正在发生变化。例如,驱动火箭发动机的软件不能期望推力,阻力,风速等从一次发射到下一次发射完全相同

(因此,令我惊讶的是,您相信或希望实时系统是确定性的;它们从未如此!也许您关心的是WCET,由于缓存的原因,它越来越难预测)

顺便说一句,一些“实时”或“嵌入式”系统正在实现自己的malloc(或某些)变体。C ++程序可以具有其分配器-s,可由标准容器s使用。另请参见thisthat,等等,等等。

嵌入式软件的高级层(例如自动驾驶汽车及其计划软件)肯定会使用堆分配,甚至可能使用垃圾收集技术(其中一些是“实时”的),但通常不被认为对安全性至关重要。


6
我认为OP的意思是说,使用完全相同的输入运行两次程序应导致相同的执行路径或相同的可观察行为(或任何其他首选的定义)。
Toby Speight

但是,“观察到的行为”是主观的(什么调试printf%p的结果malloc),并可能导致激烈的讨论
巴西莱Starynkevitch

自动驾驶汽车的计划人员(或实际上任何其他对安全性至关重要的汽车软件)都不会使用堆分配或垃圾收集。MISRA规则禁止动态分配内存。
dasdingonesin

我从AI的角度使用计划。所有用于计划的AI软件都使用堆分配(其中大多数使用垃圾收集并以非常高级的AI语言(可能是Lisp,Prolog等)进行编码。)当然,它们不是此类系统的安全关键层
Basile Starynkevitch

12

tl; dr:并不是动态内存分配本质上是不确定的(正如您根据相同的执行路径定义的那样);这通常会使您的程序变幻莫测。特别是,您无法预测在面对任意输入序列时分配器是否可能失败。

您可能有一个不确定的分配器。实际上,这在您的实时世界之外很普遍,在实时世界中,操作系统使用诸如地址布局随机化之类的东西。当然,这会使您的程序不确定。

但这不是一个有趣的情况,因此让我们假设一个完全确定性的分配器:相同的分配和释放顺序序列将始终在相同的位置产生相同的块,并且那些分配和释放将始终具有有限的运行时间。

现在您的程序可以是确定性的:相同的一组输入将导致完全相同的执行路径。

问题在于,如果要响应输入来分配和释放内存,则无法预测分配是否会失败(并且失败不是一种选择)。

首先,您的程序可能会泄漏内存。因此,如果需要无限期运行,最终分配将失败。

但是,即使您可以证明没有泄漏,您也需要知道,从来没有一个输入序列会比可用内存占用更多​​的内存。

但是,即使您可以证明程序永远不会需要的内存超过可用内存,分配器也可能会根据分配和释放的顺序来分割内存,从而最终无法找到连续的块来满足分配,即使总体上有足够的可用内存。

很难证明没有输入序列会导致病理性分裂。

您可以设计分配器以保证不会出现碎片(例如,仅分配一个大小的块),但是这对调用者造成了实质性的约束,并可能由于浪费而增加了所需的内存量。而且,调用者必须仍然证明没有泄漏,并且无论输入顺序如何,总内存需求都有令人满意的上限。这种负担如此之大,以至于设计系统实际上更加简单,因此它不使用动态内存分配。


OP提到了太空应用(高可靠性)和基于微控制器的应用。这些的真正大问题是堆碎片以及可能发生的意外分配失败。对于微控制器上可用的较小存储空间,发生此类错误的可能性很高。为避免这种情况,应静态分配所有内存,并仔细监视最大堆栈使用量。
uɐɪ

10

实时系统的处理方法是,程序必须严格满足某些计算和内存限制,而与执行路径无关(根据输入的不同,执行路径可能仍然有很大差异)。那么,在这种情况下,使用通用动态内存分配(例如malloc / new)意味着什么?这意味着开发人员在某个时候无法确定确切的内存消耗,并且无法判断所得程序是否能够满足内存和计算能力的要求。


4
好吧,解压缩相同的压缩文件总是会得到相同的结果,但是建议存储未压缩的结果会遗漏文件压缩的​​要点;)。更严重的是,路线规划师是完全确定性程序的一个著名示例,该程序具有很小的输入空间(起点和终点),其结果矩阵太大而不能存储。
MSalters

1
这似乎并不能回答所提出的问题。它回答了其他问题。提醒一下,问题是“使用堆是否会使程序具有不确定性?” 这不能回答这个问题。它可能会回答“在实时系统中使用堆是否有问题?”的问题,但这是一个不同的问题。
DW

7

是的,这是正确的。对于您提到的那种应用程序,必须详细说明可能发生的所有事情。程序必须根据规范处理最坏的情况,并且要准确地留出足够多的内存。“我们不知道获得多少输入”的情况不存在。最坏的情况是用固定数字指定的。

您的程序必须具有确定性,从某种意义上讲,它可以处理最坏情况下的所有事情。

堆的主要目的是允许几个不相关的应用程序共享RAM内存,例如在PC中,RAM中运行的程序/进程/线程的数量不确定。在实时系统中不存在这种情况。

另外,随着段的增加或删除,堆本质上是不确定的。

此处更多信息:https : //electronics.stackexchange.com/a/171581/6102


2
“确定性可以在最坏的情况下处理所有问题”-这不是确定性一词的含义。并非所有不良工程都是不确定的。
DW

@DW如果您指定程序应该能够处理100件事,那么您将为此进行设计并期望在所有情况下(直到100)都具有确定性行为。如果超出指定的限制,则所有投注都将无效,结果不确定。这实际上就是确定性的含义。另一种选择是不设置上限和堆分配。然后,很难轻易确定程序将陷入困境的地步。这取决于堆的大小,堆的碎片以及许多其他因素。同样,如果您允许“任意数量的输入”而不是确定的最大值。
伦丁

5

即使您的堆分配器具有可重复的行为(相同的分配序列和免费调用产生相同的块序列,因此(希望)是相同的内部堆状态),如果更改了调用序列,堆的状态也可能发生巨大变化,可能导致碎片化,从而以无法预测的方式导致内存分配失败。

尤其是在嵌入式系统中,完全禁止使用堆分配的原因。诸如飞机或航天器制导系统或生命支持系统之类的关键任务系统无法测试在响应固有异步事件时可能发生的malloc / free呼叫序列中的所有可能变化。

解决方案是,每个处理程序都为其目的留出一个内存,并且以什么顺序调用这些处理程序不再重要(至少就内存使用而言)。


3

在硬实时软件中使用堆的问题是堆分配可能失败。当您用尽堆时,您会做什么?

您正在谈论太空应用。您有非常严格的不失败要求。您一定不能泄漏内存,因此至少没有足够的安全模式代码可以运行。你一定不能摔倒。您不得抛出没有catch块的异常。您可能没有具有受保护内存的操作系统,因此理论上一个崩溃的应用程序可以删除所有内容。

您可能根本不想使用堆。收益不超过整个计划的成本。

非确定性通常意味着其他内容,但是在这种情况下,最好的阅读方法是它们希望整个程序的行为完全可预测。


2

从GHS引入Integrity RTOS:

https://www.ghs.com/products/rtos/integrity.html

和LynxOS:

http://www.lynx.com/products/real-time-operating-systems/lynxos-178-rtos-for-do-178b-software-certification/

LynxOS和Integrity RTOS属于航天应用,导弹,飞机等中使用的软件,因为许多其他未经主管部门(例如FAA)批准或认证。

https://www.ghs.com/news/230210r.html

为了满足严格的空间应用标准,Integrity RTOS实际上提供形式验证,即经过数学验证的逻辑,以证明其软件符合规范。

在这些条件中,请从这里引用:

https://zh.wikipedia.org/wiki/完整性(操作系统)

和这里:

Green Hills Integrity动态内存分配

这是:

在此处输入图片说明

我不是正式方法的专家,但是此验证的要求之一可能是消除内存分配所需时间的不确定性。在RTOS中,所有事件之间的精确间隔都是毫秒。动态内存分配始终在所需的时序方面存在问题。

从数学上来说,您真的需要从关于时间和内存量的最基本的假设中证明一切正常。

如果您想到堆内存的替代方案: 静态内存。地址是固定的,分配的大小是固定的。内存中的位置是固定的。因此,很容易就内存充足性,可靠性,可用性等进行推理。


1
从技术上讲,作为一个封闭系统,它是确定性的,但也很混乱。无法预测。
约翰·吴

2

简短答案

例如第一级或第二级触发闪烁器设备的数据值或其统计不确定性分布会受到一些影响,这些影响可能源于您可能需要等待的不可复制的时间量malloc/free

最糟糕的方面是,它们与硬件的物理现象无关,而与内存的状态(及其历史记录)无关。

在这种情况下,您的目标是从受这些错误影响的数据中重建事件的原始序列。重建/猜测的序列也会受到错误的影响。这种迭代并非总是会收敛于一个稳定的解决方案。并不是说那将是正确的;您的数据不再独立...您将面临逻辑短路的风险...

更长的答案

您说“当人们告诉我时,我无法验证此声明的正确性”
我将尝试给您一个纯粹的假设情况/案例研究。

让我们想象一下,您在系统上使用CCD或处理一些第一级和第二级闪烁器触发器,这些触发器必须节省资源(您在太空中)。
将设置采集速率,以使背景x%MAXBINCOUNT

  • 有一个爆发,您的计数出现尖峰,而bin计数器溢出。
    我全部想要:您切换到最大采集速率,然后完成缓冲区。
    在完成额外的缓冲区的同时,您将释放/分配更多的内存。
    你会怎么做?

    1. 您将使反作用力有溢出的风险(第二层将尝试正确计算数据包的时间),但是在这种情况下,您将低估该时间段的计数吗?
    2. 您将停止在时间序列中引入漏洞的计数器吗?

    注意:

    • 等待分配,您将丢失瞬态(或至少是其开始)。
    • 无论您做什么,都取决于您的内存状态,并且它是不可复制的。
  • 现在,信号maxbincount以您的硬件允许的最大采集速率在附近变化,并且事件比平时更长。
    您完成了空间并要求更多...同时,您遇到了上述相同的问题。
    溢出和系统性峰计算出时间序列中的低估还是空洞?

让我们移动第二级(它也可以在第一级触发器上)。

通过硬件,您收到的数据量超出了库存或传输的范围。
您必须在时间或空间上对数据进行聚类(2x2、4x4,... 16x16 ... 256x256 ...像素缩放...)。

前一个问题的不确定性可能会影响误差分布
对于CCD设置,您的边框像素的计数接近。maxbincount(取决于您希望看到的更好的“位置”)。
现在您可以在CCD上淋浴或在一个大点上进行淋浴,而计数总数相同但统计不确定性不同(等待时间引入的部分)...

因此,例如,在期望有洛伦兹轮廓的情况下,您可以使用高斯一个(Voigt)获得其卷积,或者如果第二个在肮脏的高斯中真正占主导地位,则可以进行卷积...


-3

总会有一个权衡。程序的运行环境及其执行的任务应成为决定是否应使用HEAP的基础。

当您要在多个函数调用之间共享数据时,堆对象是有效的。您只需要传递指针,因为可以全局访问堆。也有缺点。某些功能可能会释放此内存,但其他地方也可能存在一些引用。

如果堆内存在工作完成后仍未释放,并且程序继续分配更多的内存,则HEAP有时会耗尽内存并影响程序的确定性。


1
“由于堆是可全局访问的,因此只需要传递指针即可。” 这.data.bss如何不同?
伦丁

5
一个可以创建全局堆栈变量的方法……这有什么问题?它们也可以在函数之间传递。
量子物理学家

2
这似乎并不能回答所提出的问题。它回答了其他问题。提醒一下,问题是“使用堆是否会使程序具有不确定性?” 这不能回答这个问题。它可能会回答“在实时系统中使用堆是否是个好主意?”这个问题,但这是一个不同的问题。最后一个短语开始朝这个方向发展,但是您没有说它如何影响程序的确定性,或者为什么,也没有回答这个问题。
DW
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.