简体   繁体   中英

Catching "Stack Overflow" exceptions in recursive C++ functions

Is it possible to catch a stack overflow exception in a recursive C++ function? If so, how?

so what will happen in this case

void doWork()
{

     try() {

     doWork();    
     }


     catch( ... )  {

     doWork();    
     }
}  

I am not looking for an answer to specific OS. Just in general

It's not an exception per se, but if you just want to be able to limit your stack usage to a fixed amount, you could do something like this:

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

There's really no portable way to do it. An out of control recursive function will usually cause an invalid memory access when it tries to allocate a stack frame beyond the stack address space. This will usually just crash your program with a Segmentation Fault/Access Violation depending on the OS. In other words, it won't throw a c++ exception that can be handled in a standard way by the language.

There isn't a portable way. However, there are a few nonportable solutions.

First, as others have mentioned, Windows provides a nonstandard __try and __except framework called Structured Exeption Handling ( your specific answer is in the Knowledge Base).

Second, alloca -- if implemented correctly -- can tell you if the stack is about to overflow:

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

I like this approach, because at the end of probe_stack , the memory alloca allocated is released and available for your use. Unfortunately only a few operating systems implement alloca correctly. alloca never returns NULL on most operating systems, letting you discover that the stack has overflown with a spectacular crash.

Third, UNIX-like systems often have a header called ucontext.h with functions to set the size of the stack (or, actually, to chain several stacks together). You can keep track of where you are on the stack, and determine if you're about to overflow. Windows comes with similar abilities a la CreateFiber .


As of Windows 8, Windows has a function specifically for this ( GetCurrentThreadStackLimits )

Even if you can do this non-portably, as you can in Windows, it's still a bad idea.糟糕的主意。 The best strategy is to not overflow the stack in the first place. If you need isolation from some code you don't control, run that code in a different process and you can detect when it crashes. But you don't want to do that sort of thing in your own process, because you don't know what sort of nasty corruption of state the offending code is going to do, and that will make you unstable.

There's an interesting, somewhat related blog post by Microsoft's Raymond Chen about why you shouldn't try to check for valid pointers in a user mode application on Windows.

On what OS? Just for example, you can do it on Windows using Structured Exception Handling (or Vectored Exception Handling). Normally you can't do it with native C++ exception handling though, if that's what you're after.

Edit: Microsoft C++ can turn a structured exception into a C++ exception. That was enabled by default in VC++ 6. It doesn't happen by default with newer compilers, but I'm pretty sure with a bit of spelunking, you could turn it back on.

It's true that when this happens, you're out of stack space. That's part of why I mentioned vectored exception handling. Each thread gets its own stack, and a vectored exception handler can run in a separate thread from where the exception was thrown. Even SEH, however, you can handle a stack overflow exception -- it just has to manually spawn a thread to do most of the work.

I doubt so, when stack got overflow the program will not be able even to handle exception. Normally OS will close such program and report the error.
This happens mostly because of infinite recursions.

In Windows you can use structured exception handling (SEH), with __try and __except keywords to install your own exception handler routine that can catch stack overflows, access violation, etc etc.

It's pretty neat to avoid Windows' default crash dialog, and replace it with your own, if you need to.

This is done all the time by most modern operating systems. If you want to do it on your own, you'll have to know the maximum "safe" address for your stack (or likewise do some math to determine how many times you can safely call the function), but this can get very tricky if you aren't managing the call stack yourself, since the OS will usually (for good reason) be hiding this from you.

If you are programming in kernel space, this gets significantly easier, but still something I question why you're doing. If you have a stack overflow, it's probably because of a bad algorithmic decision or else an error in the code.

edit: just realized you want to "catch the exception" that results. I don't think my answer directly answers that at all (does this exception even exist? i would figure instead on a spectacular failure), but I'll leave it up for insight. If you want it removed, please let me know in the comments and I will do so.

You have to know always a level of your recursion and check it if greater than some threshold. Max level (threshold) is calclulated by ratio of stack size divided by the memory required one recursive call.

The memory required one recursive call is the memory for all arguments of the function plus the memory for all local variables plus the memory for return address + some bytes (about 4-8).

Of course, you could avoid the recursion problem by converting it to a loop.

Not sure if you're aware of this but any recursive solution can be translated to a loop-based solution, and vice-versa. It is usually desirable to use a loop based solution because it is easier to read and understand.

Regardless of use of recursion or loop, you need to make sure the exit-condition is well defined and will always be hit.

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

Then, run your application...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM