在递归C ++函数中捕获“堆栈溢出”异常

时间:2009-10-16 15:43:51

标签: c++ exception exception-handling recursion

是否可以在递归C ++函数中捕获stack overflow exception?如果是这样,怎么样?

所以在这种情况下会发生什么

void doWork()
{

     try() {

     doWork();    
     }


     catch( ... )  {

     doWork();    
     }
}  

我不是在寻找特定操作系统的答案。一般来说

11 个答案:

答案 0 :(得分:11)

实际上没有便携式方式。失控的递归函数在尝试将堆栈帧分配到堆栈地址空间之外时,通常会导致无效的内存访问。这通常会使程序崩溃,因为操作系统会出现分段错误/访问冲突。换句话说,它不会抛出可以通过语言以标准方式处理的c ++异常。

答案 1 :(得分:11)

本身并不例外,但如果您只想将堆栈使用量限制为固定金额,则可以执行以下操作:

#include <stdio.h>

// These will be set at the top of main()
static char * _topOfStack;
static int _maxAllowedStackUsage;

int GetCurrentStackSize()
{
   char localVar;
   int curStackSize = (&localVar)-_topOfStack;
   if (curStackSize < 0) curStackSize = -curStackSize;  // in case the stack is growing down
   return curStackSize;
}

void MyRecursiveFunction()
{
   int curStackSize = GetCurrentStackSize();
   printf("MyRecursiveFunction:  curStackSize=%i\n", curStackSize);

   if (curStackSize < _maxAllowedStackUsage) MyRecursiveFunction();
   else
   {
      printf("    Can't recurse any more, the stack is too big!\n");
   }
}

int main(int, char **)
{
   char topOfStack;
   _topOfStack = &topOfStack;
   _maxAllowedStackUsage = 4096;  // or whatever amount you feel comfortable allowing

   MyRecursiveFunction();
   return 0;
}

答案 2 :(得分:6)

没有可移植的方式。但是,有一些不便携的解决方案。

首先,正如其他人所提到的,Windows提供了一个名为Structured Exeption Handling的非标准__try__except框架(your specific answer位于知识库中)。

其次,alloca - 如果正确实施 - 可以告诉你堆栈是否即将溢出:

bool probe_stack(size_t needed_stack_frame_size)
{
    return NULL != alloca(needed_stack_frame_size);
};

我喜欢这种方法,因为在probe_stack的末尾,分配的内存alloca已经发布并可供您使用。不幸的是,只有少数操作系统正确实现allocaalloca永远不会在大多数操作系统上返回NULL,让您发现堆栈已经溢出并发生了​​壮观的崩溃。

第三,类UNIX系统通常有一个名为ucontext.h的标题,其中包含设置堆栈大小的功能(实际上,将多个堆栈链接在一起)。您可以跟踪堆栈中的位置,并确定是否即将溢出。 Windows具有类似的功能 a la CreateFiber


从Windows 8开始,Windows有function specifically for thisGetCurrentThreadStackLimits

答案 3 :(得分:6)

即使你可以像在Windows中那样非移植地执行此操作,但它仍然是一个 非常 坏主意。最好的策略是首先不要溢出堆栈。如果您需要从一些您无法控制的代码中隔离,请在不同的进程中运行该代码,并且可以检测它何时崩溃。但是你不想在你自己的过程中做那种事情,因为你不知道违规代码将要做什么样的令人讨厌的腐败状态,这会让你不稳定。

微软的Raymond Chen有一个有趣的,有点相关的blog post,关于为什么你不应该尝试在Windows上检查用户模式应用程序中的有效指针。

答案 4 :(得分:4)

在什么操作系统上?例如,您可以使用结构化异常处理(或向量异常处理)在Windows上执行此操作。通常情况下,你无法使用本机C ++异常处理,如果这就是你所追求的。

编辑:Microsoft C ++可以将结构化异常转换为C ++异常。这在VC ++ 6中是默认启用的。默认情况下,新的编译器不会发生这种情况,但我非常肯定会有一些洞察,你可以重新开启。

确实,当发生这种情况时,你就会失去堆叠空间。这就是我提到矢量异常处理的部分原因。每个线程都有自己的堆栈,向量异常处理程序可以在抛出异常的单独线程中运行。但是,即使是SEH,您也可以处理堆栈溢出异常 - 它只需手动生成一个线程来完成大部分工作。

答案 5 :(得分:1)

我怀疑,当堆栈溢出时,程序甚至无法处理异常。通常,OS会关闭此类程序并报告错误 这主要是因为无限递归。

答案 6 :(得分:1)

在Windows中,您可以使用结构化异常处理(SEH),使用__try和__except关键字来安装您自己的异常处理程序例程,该例程可以捕获堆栈溢出,访问冲突等等。

如果需要,可以避免使用Windows的默认崩溃对话框,并将其替换为您自己的对话框。

答案 7 :(得分:0)

大多数现代操作系统始终如此。如果你想自己做,你必须知道你的堆栈的最大“安全”地址(或者同样做一些数学来确定你可以安全地调用该函数的次数),但这可能会非常棘手如果您不是自己管理调用堆栈,因为操作系统通常会(出于正当理由)将此隐藏起来。

如果你在内核空间编程,这会变得非常容易,但我仍然会质疑你为什么这么做。如果你有堆栈溢出,可能是因为算法判断错误或者代码中出现错误。

编辑:刚刚意识到你想要“抓住异常”的结果。我认为我的答案根本没有直接回答这个问题(这个例外是否存在?我会想到一个壮观的失败),但我会留下深刻见解。如果你想删除它,请在评论中告诉我,我会这样做。

答案 8 :(得分:-1)

您必须始终知道递归的级别,并检查是否超过某个阈值。最大级别(阈值)由堆栈大小除以一次递归调用所需的内存的比率计算。

需要一个递归调用的内存是函数的所有参数的内存加上所有局部变量的内存加上返回地址的内存+一些字节(大约4-8)。

答案 9 :(得分:-1)

当然,您可以通过将其转换为循环来避免递归问题。

不确定您是否意识到这一点,但任何递归解决方案都可以转换为基于循环的解决方案,反之亦然。通常需要使用基于循环的解决方案,因为它更易于阅读和理解。

无论使用递归还是循环,都需要确保退出条件定义良好且始终会被命中。

答案 10 :(得分:-1)

If you use Visual C++
Goto C/C++ , Code Generation
Choose "Both..." in "Basic Runtime Checks"

然后,运行您的应用程序......