在堆栈已满之前,C / C ++中的最大递归函数调用会产生分段错误吗?

时间:2015-05-13 07:19:53

标签: c++ recursion segmentation-fault

我在做一个问题,我用一个递归函数来创建一个分段树。对于较大的值,它开始给出分段错误。所以我想之前可能是因为数组索引值超出了界限但后来我认为可能是因为程序堆栈太大了。 我编写了这段代码来计算系统给出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);

}

运行上面的代码后,我得到v的值为261926和261893和261816,然后才得到分段错误,所有值都接近这些。

现在我知道这将取决于机器到机器,以及被调用的函数堆栈的大小,但是有人可以解释如何保护安全免受seg-fault的基础知识以及什么是软限制请记住。

3 个答案:

答案 0 :(得分:5)

(AFAIK)没有完善的限制。 (我从Linux桌面的角度回答)。

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

使用pthread_create(3)创建线程时,您可以使用显式pthread_attr_t并使用pthread_attr_setstack(3)来设置堆栈空间。

BTW,最近GCC,您可以使用split stacks编译所有软件(包括标准C库)(因此将-fsplit-stack传递给{{ 1}}或gcc

最后你的例子是tail call,GCC可以优化它(用参数跳转)。我检查过如果用g++编译(在Linux / x86-64 / Debian上使用GCC 4.9.2),递归将转换为一个真正的循环,没有堆栈分配会无限增长(你的程序运行近40个一分钟内有数百万次调用g++ -O2,然后我打断了它。)在更好的语言中,如Scheme或Ocaml,可以保证尾调用确实是迭代编译的(然后尾递归调用成为通常 - 甚至是唯一的 - 循环构造)。

CyberSpok的评论过多(暗示避免递归)。递归非常有用,但你应该将它们限制在合理的深度(例如几千),你应该注意call stack上的调用帧很小(每个小于一千字节),所以实际上是分配和解除分配C heap中的大部分数据。 GCC -fstack-usage选项对报告每个已编译函数的堆栈使用非常有用。请参阅thisthat个答案。

请注意,continuation passing style是将递归转换为迭代的规范方法(然后使用动态分配的closures交换堆栈帧。)

一些聪明的算法用花哨的修改迭代来代替递归,例如: Deutche-Shorr-Waite graph marking algorithm

答案 1 :(得分:4)

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

在无限深度进行递归并不是一个好主意,您应该考虑将代码更改为“它不会那样做”。您需要了解算法并了解它将递归的深度,以及系统中是否可以接受。遗憾的是,当堆栈用完时没有任何人可以做任何事情(充其量你的程序崩溃,最坏的情况下它没有,但反而导致某些ELSE出错,比如堆栈或其他一些应用程序的堆搞砸了! )

在桌面计算机上,我认为可以接受一百到几千的递归深度,但不会超过这个 - 这就是如果你在每个调用中使用少量堆栈 - 如果每个call正在使用堆栈的千字节,你应该进一步限制调用级别,或者减少对堆栈空间的需求。

如果你需要有更多的递归深度,你需要重新安排代码 - 例如使用软件堆栈来存储状态,以及代码本身的循环。

[1]在你发布的代码中使用g ++ -O2,我得到了5000万并计数,我希望如果我留下足够长的时间,它会重新启动为零,因为它会一直持续下去 - 这是因为g ++检测到这一点递归可以转换为循环,并做到这一点。使用-O0或-O1编译的相同程序确实停止在200000以上。使用clang ++ -O1它会继续运行。 clang编译的代码仍在运行,因为我完成了剩余代码的编写,以1.85亿次“递归”。

答案 2 :(得分:0)

对于基于Linux的应用程序,我们可以使用getrlimit和setrlimit API来了解各种内核资源限制,例如核心文件的大小,cpu时间,堆栈大小,漂亮的值,最大值。没有。 “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;
   }
}