C++ C/C+中的最大递归函数调用数+;在堆栈已满并出现分段错误之前?

C++ C/C+中的最大递归函数调用数+;在堆栈已满并出现分段错误之前?,c++,recursion,segmentation-fault,C++,Recursion,Segmentation Fault,我在做一个问题,我使用了一个递归函数来创建一个段树。对于较大的值,它开始给出分段错误。所以我以前认为可能是因为数组索引值超出了范围,但后来我认为可能是因为程序堆栈太大了。 我编写这段代码是为了计算在系统给出seg fault之前允许的最大递归调用数 #include<iostream> using namespace std; void recur(long long int); int main() { recur(0); return 0; } void recur(l

我在做一个问题,我使用了一个递归函数来创建一个段树。对于较大的值,它开始给出分段错误。所以我以前认为可能是因为数组索引值超出了范围,但后来我认为可能是因为程序堆栈太大了。 我编写这段代码是为了计算在系统给出seg fault之前允许的最大递归调用数

#include<iostream>
using namespace std; 
void recur(long long int);
int main()
{
  recur(0);
  return 0;
}
void recur(long long int v)
{
  v++;
  cout<<v<<endl;
  recur(v);

}
#包括
使用名称空间std;
无效重现(长整型);
int main()
{
复发(0);
返回0;
}
无效重现(长整数v)
{
v++;
cout没有明确的限制(我是从Linux桌面的角度回答的)

在台式机、笔记本电脑上,2015年的默认堆栈大小为几兆字节。在Linux上,您可以使用更改堆栈大小(将其更改为合理的数字,不要期望这些天能够将其设置为千兆字节),还可以使用或解析
/proc/self/limits
(请参阅)在嵌入式微控制器上,或者在Linux内核中,整个堆栈可能会受到更大的限制(总共只有几千字节)

使用创建线程时,可以使用显式的
pthread\u attr\u t
并使用设置堆栈空间

顺便说一句,使用最新版本,您可能会编译所有软件(包括标准C库)(因此将
-fsplit stack
传递到
gcc
g++

最后,您的示例是一个,GCC可以优化它(变成带参数的跳转)。我检查过,如果您使用
g++-O2
(在Linux/x86-64/Debian上使用GCC4.9.2)编译,递归将转换为真正的循环,并且不会无限增加堆栈分配(您的程序在一分钟内运行了近4000万次对
的调用,然后我打断了它)在Scheme或Ocaml等更好的语言中,可以保证尾部调用确实是迭代编译的(这样尾部递归调用就成了通常(甚至是唯一)的循环构造)

在他的评论中是过度的(暗示要避免递归)。递归非常有用,但您应该将其限制在合理的深度(例如几千),并且您应该注意,上的调用帧很小(每个小于一KB),因此实际分配和取消分配中的大部分数据。GCC选项对于报告每个已编译函数的堆栈使用情况非常有用。请参阅和回答

请注意,这是一种将递归转换为迭代的规范方法(然后用动态分配的堆栈帧进行交换)


一些聪明的算法用奇特的修改迭代来代替递归,例如。

可以执行的递归级别的数量取决于调用堆栈的大小以及放置在这样一个堆栈上的局部变量和参数的大小。除了“如何编写代码”,就像许多其他与内存相关的东西一样,这在很大程度上取决于您运行的系统、您使用的编译器、优化级别[1],等等。一些我工作过的嵌入式系统,堆栈只有几百个字节,我的第一台家用计算机有256个字节的堆栈,而现代台式机有兆字节的堆栈(你可以调整它,但最终会耗尽)

以无限深度进行递归不是一个好主意,您应该考虑将代码更改为“它不会这样做”。您需要了解算法,了解它将递归到什么深度,以及它在您的系统中是否可以接受。不幸的是,在堆栈耗尽时,任何人都无法做到(在最好的情况下,您的程序会崩溃,在最坏的情况下不会崩溃,但会导致其他一些错误,例如其他应用程序的堆栈或堆被弄乱!)

在桌面计算机上,我认为递归深度为100到数千是可以接受的,但不超过这个,也就是说,如果每次调用中堆栈的使用量很小,如果每次调用都使用了数千字节的堆栈,那么应该进一步限制调用级别,或者减少对堆栈空间的需要

如果需要更多的递归深度,则需要重新安排代码-例如,使用软件堆栈存储状态,并在代码本身中使用循环


[1]在你发布的代码中使用g++-O2,我得到了5000万并开始计数,我希望如果我把它保留足够长的时间,它会在零处重新启动,因为它会永远继续运行-这是因为g++检测到这个递归可以转换成循环,并且会这样做。使用-O0或-O1编译的同一个程序确实会在200000多一点的时候停止。使用clang++-O1它一直在运行。当我完成其余代码的编写时,编译后的代码仍在运行,达到1.85亿次“递归”.

对于基于Linux的应用程序,我们可以使用getrlimit和setrlimit API了解各种内核资源限制,如核心文件大小、cpu时间、堆栈大小、nice值、最大进程数等。“RLIMIT_stack”是Linux内核中定义的堆栈的资源名称。下面是检索堆栈大小的简单程序:

#include <iostream>
#include <sys/time.h>
#include <sys/resource.h>
#include <errno.h>
using namespace std;

int main()
{
   struct rlimit sl;
   int returnVal = getrlimit(RLIMIT_STACK, &sl);
   if (returnVal == -1)
   {
      cout << "Error. errno: " << errno << endl;
   }
   else if (returnVal == 0)
   {
      cout << "stackLimit soft - max : " << sl.rlim_cur << " - " << sl.rlim_max << endl;
   }
}
#包括
#包括
#包括
#包括
使用名称空间std;
int main()
{
struct-rlimit-sl;
int returnVal=getrlimit(RLIMIT_堆栈,&sl);
如果(returnVal==-1)
{

为了避免seg故障,不要使用递归。当你创建一棵树时,堆栈不应该比树长得更深,所以除非你的树很大而且不平衡(或者你的局部变量和参数很大),否则不会有问题。递归只是一种“幻想”循环,大多数递归函数都可以替换为循环。如果必须使用递归,不要使用大的本地数组,并确保停止条件在堆栈用完之前有效,并且只有在最坏的情况下,并且作为最后的手段,才可以增加堆栈。根据经验,递归深度O(log N)没有问题,但是是一个递归