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

Allocating on heap vs allocating on stack in recursive functions

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

答案取决于您的应用领域和平台的具体情况。 "Embedded system" 是模糊词。 big oscilloscope running MS Windows or Linux 位于光谱的一端。它的大部分都可以像普通 PC 一样进行编程。他们不应该失败,但如果失败,只需重新启动即可。

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

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

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

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

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

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

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

When I'm defining a recursive function, is it better/safer to allocate local variables on heap, and then clean them up before the function returns, than to allocate them on stack.

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

在C++中,最好使用堆,尽管它的效率稍低。这是因为如果 运行 堆内存不足,new 可能会失败。在失败的情况下,new 将抛出异常。但是,运行ning out of stack space 不会导致异常,这是原因之一 alloca is frowned upon in C++.

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

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

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