在递归函数中在堆上分配与在堆栈上分配

Posted

技术标签:

【中文标题】在递归函数中在堆上分配与在堆栈上分配【英文标题】:Allocating on heap vs allocating on stack in recursive functions 【发布时间】:2015-10-02 14:43:07 【问题描述】:

当我定义一个递归函数时,在堆上分配局部变量,然后在函数返回之前清理它们,而不是在堆栈上分配它们是否更好/更安全。嵌入式系统的堆栈大小非常有限,当递归运行太深时,存在堆栈溢出的危险。

【问题讨论】:

你可以把递归变成一个循环。 我想你自己回答了你的问题。 我认为您最好提供一些详细信息,因为按照目前的写作方式,您具体要问什么还不清楚。 在适当的系统中,堆栈和堆彼此相向增长,因此您将在大约相同的递归深度处用完,尽管堆往往有开销而堆栈没有。在不正确的系统上使用更大的。 您不应该在嵌入式系统上使用递归或堆分配,期间。如果您发现自己需要其中任何一个,那么大约 99.9% 的可能性是由于混乱的程序设计和过度工程造成的。在嵌入式系统和there is not a single case where you need to use heap allocation 中使用递归是有意义的极少数情况。 【参考方案1】:

答案取决于您的应用程序域和平台的具体情况。 “嵌入式系统”是一个模糊的术语。 big oscilloscope running MS Windows or Linux 位于频谱的一端。其中大部分都可以像普通 PC 一样进行编程。它们不应该失败,但如果失败了,只需重新启动它们即可。

另一端是安全关键领域的控制器。考虑these Siemens switches,它必须在所有情况下都在几毫秒内做出反应。失败不是一种选择。他们可能不运行 Windows。可用资源有限。编程规则和程序在这里大不相同。

现在让我们检查一下您拥有的选项,递归(或不使用!)动态或自动内存分配。

在性能方面,堆栈要快得多,因此是首选。动态分配还涉及一些用于记账的内存开销,如果数据单元很小,这会很大。并且可能存在自动内存无法发生的内存碎片问题(尽管导致碎片的场景 - 对象的不同生命周期 - 如果没有动态分配,可能无法直接解决)。

但确实在某些系统上堆栈大小(远)小于堆大小;您必须阅读系统上的内存布局。示波器将有大量内存和大堆栈;电源开关不会。

如果您担心内存不足,我会按照 Christian 的建议完全避免递归,而是使用循环。迭代可能只是保持内存使用量不变。此外,递归总是使用堆栈空间,例如返回地址和-value。动态分配“局部”变量的想法只对较大的数据结构有意义,因为您仍然必须将指向数据的指针作为自动变量保存,这也会占用堆栈上的空间(并且会增加整体内存占用) .

一般来说,在资源有限的系统上,限制程序的最大资源使用量很重要。时间也是一种资源;动态分配使实时几乎不可能。

应用领域规定了安全要求。在安全关键领域(起搏器!),程序绝对不能失败。除非程序是微不足道的,否则这个理想是不可能实现的,但要付出巨大的努力才能接近。在其他领域中,程序可能会在它无法处理的情况下以定义的方式失败,但它不能无声地失败或以未定义的方式失败(例如,通过覆盖数据)。例如,与其动态分配未知数量的数据,不如使用预先定义的固定大小的数据数组来代替,并使用数组中的元素进行绑定检查。

【讨论】:

【参考方案2】:

当我定义一个递归函数时,在堆上分配局部变量,然后在函数返回之前清理它们,而不是在堆栈上分配它们是否更好/更安全。

您同时拥有 C 和 C++ 标签。这对双方来说都是一个有效的问题,但我只能对 C++ 发表评论。

在 C++ 中,最好使用堆,尽管它的效率稍低。这是因为如果堆内存用完,new 可能会失败。在失败的情况下,new 会抛出异常。但是,用完堆栈空间并不会导致异常,这也是alloca is frowned upon in C++的原因之一。

【讨论】:

【参考方案3】:

我认为一般来说你应该避免在嵌入式系统中完全递归,因为每次调用函数时,返回地址都会被压入堆栈。这可能会导致意外溢出。尝试切换到循环。

回到你的问题,mallocing 会更慢但更安全。如果没有堆空间,则 malloc 将返回错误,您可以安全地清理内存。由于 malloc 可能会非常慢,因此这会以很大的速度为代价。

如果您提前知道预期的迭代次数,则可以选择分配您需要的变量数组。这样你就只会 malloc 一次,这样可以节省时间,并且不会冒着意外填满堆或堆栈的风险。此外,您将只剩下一个可释放的变量。

【讨论】:

以上是关于在递归函数中在堆上分配与在堆栈上分配的主要内容,如果未能解决你的问题,请参考以下文章

是应该在堆栈还是堆上分配pthread函数参数?

我应该啥时候在堆上分配? (C++)

为什么编译器无法自动优化常规递归?

Unix系统编程()在堆上分配内存

有没有办法直接在堆上分配一个标准的 Rust 数组,完全跳过堆栈?

为啥函数执行后没有释放堆上的元素?