我有一个完全相同的问题,与我阅读Alex Allain的《跳入C ++》,第16章:递归,第230页,因此我进行了一些测试。
TLDR;
我的Arduino Nano(ATmega328 mcu)在堆栈溢出并崩溃之前可以进行211个递归函数调用(针对下面给出的代码)。
首先,让我解决这个问题:
有时,递归是实现特定算法的唯一快速选择。
[更新:啊,我略读了“快速”一词。在这种情况下,您具有一定的有效性。不过,我认为值得一提。
不,我认为这不是真实的说法。我敢肯定,所有算法无一例外都具有递归和非递归解决方案。只是有时候它要容易得多使用递归算法。话虽这么说,递归在微控制器上的使用却很受限制,并且可能永远不会在安全关键代码中被允许。但是,当然可以在微控制器上进行。要知道您可以使用任何给定的递归函数的深度,只需对其进行测试!在真实的测试案例中,在真实的应用程序中运行它,并删除基本条件,以便无限递归。打印一个计数器,亲自了解您可以走多深,以便您知道递归算法是否将RAM的限制推得太近而无法实际使用。以下是在Arduino上强制堆栈溢出的示例。
现在,请注意以下几点:
您可以获得多少个递归调用或“堆栈帧”取决于许多因素,包括:
- 您的RAM的大小
- 堆栈中已经有多少东西或堆中已经占用了多少东西(即:您的可用RAM很重要;
free_RAM = total_RAM - stack_used - heap_used
或者,您可能会说free_RAM = stack_size_allocated - stack_size_used
)
- 每个新的递归函数调用将放置在堆栈上的每个新“堆栈框架”的大小。这取决于调用的函数及其变量和内存要求等。
我的结果:
- 20171106-2054hrs-东芝Satellite w / 16 GB RAM; 四核,Windows 8.1:崩溃前打印的最终值:43166
- 20180306-1913hrs带64 GB RAM的戴尔高端笔记本电脑; 8核,Linux Ubuntu 14.04 LTS:崩溃前打印的最终值:261752
- 后面跟着这句话
Segmentation fault (core dumped)
- 只花了约4〜5秒时间就崩溃了
- 20180306-1930hrs Arduino Nano:TBD ---处于〜250000且仍在计数--- Arduino优化设置必须已使它优化了递归... ??? 是的,就是这种情况。
- 添加
#pragma GCC optimize ("-O0")
到文件顶部并重做:
- 20180307-0910hrs Arduino Nano:32 kB闪存,2 kB SRAM,16 MHz处理器或:崩溃前打印的最终值:211
Here are the final print results:
209
210
211 ⸮ 9⸮ 3⸮
- 它开始以115200串行波特率打印时仅花费了几分之一秒-可能是1/10秒
- 2 kiB = 2048字节/ 211堆栈帧= 9.7字节/帧(假设堆栈使用的是所有RAM,实际上不是这种情况)-但这似乎还是很合理的。
代码:
PC应用程序:
/*
stack_overflow
- a quick program to force a stack overflow in order to see how many stack frames in a small function can be loaded onto the stack before the overflow occurs
By Gabriel Staples
www.ElectricRCAircraftGuy.com
Written: 6 Nov 2017
Updated: 6 Nov 2017
References:
- Jumping into C++, by Alex Allain, pg. 230 - sample code here in the chapter on recursion
To compile and run:
Compile: g++ -Wall -std=c++11 stack_overflow_1.cpp -o stack_overflow_1
Run in Linux: ./stack_overflow_1
*/
#include <iostream>
void recurse(int count)
{
std::cout << count << "\n";
recurse(count + 1);
}
int main()
{
recurse(1);
}
Arduino“素描”程序:
/*
recursion_until_stack_overflow
- do a quick recursion test to see how many times I can make the call before the stack overflows
Gabriel Staples
Written: 6 Mar. 2018
Updated: 7 Mar. 2018
References:
- Jumping Into C++, by Alex Allain, Ch. 16: Recursion, p.230
*/
// Force the compiler to NOT optimize! Otherwise this recursive function below just gets optimized into a count++ type
// incrementer instead of doing actual recursion with new frames on the stack each time. This is required since we are
// trying to force stack overflow.
// - See here for all optimization levels: https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
// - They include: -O1, -O2, -O3, -O0, -Os (Arduino's default I believe), -Ofast, & -Og.
// I mention `#pragma GCC optimize` in my article here: http://www.electricrcaircraftguy.com/2014/01/the-power-of-arduino.html
#pragma GCC optimize ("-O0")
void recurse(unsigned long count) // each call gets its own "count" variable in a new stack frame
{
// delay(1000);
Serial.println(count);
// It is not necessary to increment count since each function's variables are separate (so the count in each stack
// frame will be initialized one greater than the last count)
recurse (count + 1);
// GS: notice that there is no base condition; ie: this recursive function, once called, will never finish and return!
}
void setup()
{
Serial.begin(115200);
Serial.println(F("\nbegin"));
// First function call, so it starts at 1
recurse (1);
}
void loop()
{
}
参考文献:
- Alex Allain跳入C ++,第16章:递归,第230页
- http://www.electricrcaircraftguy.com/2014/01/the-power-of-arduino.html-从字面上看:我在此“项目”中引用了自己的网站,以提醒自己如何更改给定文件的Arduino编译器优化级别使用该
#pragma GCC optimize
命令,因为我知道我已经在其中记录了该命令。