2009-10-16 82 views
9

是否有可能在递归C++函数中捕获stack overflow exception?如果是这样,怎么样?在递归C++函数中捕获“堆栈溢出”异常

所以会在这种情况下

void doWork() 
{ 

    try() { 

    doWork();  
    } 


    catch(...) { 

    doWork();  
    } 
} 

我不是在寻找的答案,特定的操作系统发生什么。只是一般

+0

发布代码?使用Lint进行静态分析? – 2009-10-16 15:45:45

+0

我已经添加了“如何?”后续问题,因为否则这只是一个是/否的问题,这不是很有趣或有用。 * how *部分是什么使这是一个有用的问题。 – 2009-10-16 16:22:10

+0

不可移动,没有。你在寻找特定操作系统的答案吗? – 2009-10-16 16:51:10

回答

0

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

如果你在内核空间编程,这变得非常容易,但仍然是我质疑你为什么要做的事情。如果你有堆栈溢出,那可能是因为一个错误的算法决定或者代码中的错误。

编辑:刚才意识到你想“赶上例外”,结果。我认为我的答案根本不会直接回答这个问题(这个例外是否存在?我会以惊人的失败为榜样),但是我会留下深刻的见解。如果你想删除它,请在评论中告诉我,我会这样做。

+0

我想我应该提一提:我认为你系统上的堆栈增长“减少”。不过,如果它是相反的,那么同样的逻辑也适用。只需要颠倒比较运算符:) – 2009-10-16 16:00:22

1

我怀疑如此,当堆栈溢出程序将无法处理异常。通常OS会关闭这样的程序并报告错误。
发生这种情况主要是因为无限递归。

+1

“手柄”与“捕捉”不同。如果环境提供了一个例外,你应该能够抓住它。但这并不意味着你可以从中恢复。抓住它,记录下来,然后退出。 – 2009-10-16 16:19:34

+0

对,谢谢。 – 2009-10-17 12:18:02

4

什么是操作系统?举个例子,你可以在Windows上使用结构化异常处理(或向量异常处理)来完成。通常情况下,你不能通过本地C++异常处理来完成,如果这就是你所追求的。

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

确实,如果发生这种情况,您的堆栈空间已经不足。这就是为什么我提到了向量化异常处理的一部分。每个线程都有自己的栈,并且一个向量异常处理程序可以在异常抛出的单独线程中运行。但是,即使是SEH,您也可以处理堆栈溢出异常 - 只需手动派生一个线程即可完成大部分工作。

+0

“通常”你不能?当*可以*你用本机C++异常处理捕获堆栈溢出? – 2009-10-16 16:20:22

+0

取决于你的意思是“本地C++”,我猜。无论您在机器上拥有的C++还是标准的C++。 – 2009-10-16 16:32:51

+2

对Windows来说这并不是那么容易......因为一旦你陷入困境,你需要记住你没有任何堆栈空间;) – Goz 2009-10-16 16:34:46

10

真的没有便携式的方式来做到这一点。失控递归函数通常会在尝试分配堆栈地址空间之外的堆栈帧时导致无效的内存访问。这通常会导致程序崩溃,具体取决于操作系统是否存在分段错误/访问冲突。换句话说,它不会抛出一个可以用该语言以标准方式处理的C++异常。

+1

我对此有两个问题:1)它是否便携并不重要。 2)堆栈溢出可能比“失控”更频繁地发生。这两者都不值得赞扬,但我不知道这个答案与选票表明的一样有帮助(不是我的更好)。 – 2009-10-16 16:59:55

1

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

这是相当整洁,以避免视窗'默认崩溃对话框,如果需要,请用您自己的替换它。

+0

要小心使用这种方法通过继续前进来“处理”异常,就好像没有发生故障一样。通过这样做,你邀请了一个痛苦的世界。 – asveikau 2009-10-16 17:47:33

+0

是的。应该指出的。无论如何,我总是终止。 – Macke 2009-10-18 10:03:18

-1

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

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

0

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

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

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

+0

我强烈反对你的第二段。很多时候,递归解决方案更容易阅读和理解,而基于循环的解决方案需要堆栈处理,这会使事情变得复杂。遍历一棵二叉树是递归简单的(process left_child; process right_child;),并且很难迭代地表达。 (不幸的是,递归通常在玩具问题上演示,如计算阶乘因子,在循环中效果更好) – 2009-10-16 16:36:51

+0

虽然任何递归公式都可以转换为一系列循环,但它并不代表它是一个循环容易的事情做。例如,看看阿克曼功能。 – 2009-10-16 16:53:42

5

没有一种便携的方式。但是,有一些不重要的解决方案。

首先,正如其他人所说,Windows提供了一个非标准__try__except框架调用Structured Exeption Handlingyour specific answer是知识库)。

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

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

我喜欢这种方法,分配的内存alloca被释放并可供您使用。不幸的是,只有少数操作系统正确实施allocaalloca在大多数操作系统上永远不会返回NULL,让您发现堆栈溢出并造成严重故障。第三,类UNIX系统通常有一个名为ucontext.h的头文件,其中包含用于设置堆栈大小(或实际上将多个堆栈链接在一起)的函数。您可以跟踪堆栈中的位置,并确定您是否即将溢出。 Windows带有类似的功能a laCreateFiber


随着Windows 8中的,Windows有一个function specifically for thisGetCurrentThreadStackLimits

+1

我认为关闭程序并不是通过一个可以优雅地捕捉和恢复的信号来实现的,而是为了安全起见,更多的是:“你好!你在做什么!?停止!停止!”这可能是我以后可以查找的东西,但既然你在这里,我可以问(在一个SO问题中,如果你愿意的话):当我的过程发生这样的事件时,我所拥有的开放资源会发生什么? – 2009-10-16 17:28:05

+0

我真的不知道会发生什么,虽然我怀疑析构函数正在运行。有一个__finally构造,你可以使用,一个Java。 – 2009-10-22 05:47:01

6

即使你能做到这一点非可移植的,因为你可以在Windows,它仍然是一个非常坏主意。最好的策略是首先不要溢出堆栈。如果您需要与某些您无法控制的代码隔离,请在不同的进程中运行该代码,并且可以检测它何时崩溃。但是你不想在你自己的过程中做这样的事情,因为你不知道有问题的代码将会做什么样的恶意腐败,这会让你变得不稳定。

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

+0

真棒点/关于在不同的过程中运行堆栈广泛的东西的想法。这可能是迄今为止在这个话题上见过的最好的帖子。 – 2009-10-16 19:15:38

9

这不是一个例外本身,但如果你只是希望能够给您的堆栈使用限制以固定的金额,你可以做这样的事情:

#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; 
} 
+1

聪明:)(15个字符) – cwap 2009-11-14 12:35:44

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

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