15

Are there any general rules when using recursion on how to avoid stackoverflows?

8
  • are there ever any hard set rules in programming? Commented Mar 4, 2009 at 14:15
  • 1
    I have replied to your comment on my answer, but I don't know if SO will notify you. So do go look at the comments. If SO does notify you, tell me. I'll delete this comment. Commented Mar 4, 2009 at 14:46
  • 59
    See this thread -> stackoverflow.com/questions/610743 :) Commented Mar 4, 2009 at 15:26
  • 6
    @ZombieSheep, that's one of those jokes that takes a few seconds. :) Commented Mar 5, 2009 at 18:08
  • 5
    @ZombieSheep: This happen when you write Recursion in Google. It says Did you mean Recursion?. Commented Jul 24, 2012 at 7:46

10 Answers 10

34

How many times you will be able to recurse will depend on:

  • The stack size (which is usually 1MB IIRC, but the binary can be hand-edited; I wouldn't recommend doing so)
  • How much stack each level of the recursion uses (a method with 10 uncaptured Guid local variables will take more stack than a method which doesn't have any local variables, for example)
  • The JIT you're using - sometimes the JIT will use tail recursion, other times it won't. The rules are complicated and I can't remember them. (There's a blog post by David Broman back from 2007, and an MSDN page from the same author/date, but they may be out of date by now.)

How to avoid stack overflows? Don't recurse too far :) If you can't be reasonably sure that your recursion will terminate without going very far (I'd be worried at "more than 10" although that's very safe) then rewrite it to avoid recursion.

Sign up to request clarification or add additional context in comments.

7 Comments

I think that TCO only happens on 64-bit IIRC, for all other cases you need to still prefix the call with tail. And if you dont play with the rules, that might not even be tail called.
@leppie: Exactly the sort of rules I wasn't remembering :) I'll see if I can dig up the blog post on it.
The 32-bit section mentions "tail. prefix does not exist in the IL stream" as a reason it won't optimize the tail call; I don't believe the C# compiler ever emits the tail IL (though other compilers like F# may).
@John Price: the JIT is free to do a tail call even if you do not specify so, as long as it is safe to do so. And thanks for that link (also saw it many moons ago). Oh and F# like Scheme is fully tail recursive.
|
8

It really depends on what recursive algorithm you're using. If it's simple recursion, you can do something like this:

public int CalculateSomethingRecursively(int someNumber)
{
    return doSomethingRecursively(someNumber, 0);
}

private int doSomethingRecursively(int someNumber, int level)
{
    if (level >= MAX_LEVEL || !shouldKeepCalculating(someNumber))
        return someNumber;
    return doSomethingRecursively(someNumber, level + 1);
}

It's worth noting that this approach is really only useful where the level of recursion can be defined as a logical limit. In the case that this cannot occur (such as a divide and conquer algorithm), you will have to decide how you want to balance simplicity versus performance versus resource limitations. In these cases, you may have to switch between methods once you hit an arbritrary pre-defined limit. An effective means of doing this that I have used in the quicksort algorithm is to do it as a ratio of the total size of the list. In this case, the logical limit is a result of when conditions are no longer optimal.

3 Comments

Although I like this solution, wouldn't it be machine dependant? some systems would be able to handle more levels, and there's no way (no simple way) to determine it at run time. Also, if you're NEEDING that many stack frames, you would be crippling the program with this.
You're right, but I would argue that the limit should be logical (based on the algorithm), not physical (based on the machine capabilities). Otherwise, you're going to hit the wall if you ever parallelize.
Also, if it's not a logical limit, then it's really just arbitrary and could result in results of variable preciseness.
7

I am not aware of any hard set to avoid stackoverflows. I personally try to ensure -
1. I have my base cases right.
2. The code reaches the base case at some point.

5 Comments

Ok Ted. In recursive functions, there are two parts -1. The part that reduces the problem and calls itself --recursive case. 2.The part that represents the smallest instance of the problem, that has a (usually) trivial answer -base case.
The recursive calling ends and begins to rewind when a base case is reached. I was trying to say that the base condition should be reached at some point.
For example factorial(n) = n * (n-1) * ... * 1. Here, the base case is 1 since factorial(1) = 1. Code: factorial(n): if n ==1: return 1 else return factorial(n-1) Note -This is a bad example of using recursion -- tail recursion. Still, it gets the point accross.
So, unless n=1 is reached by the above code, it will recurse infinitely and cause a stack overflow
4

If you're finding yourself generating that many stack frames, you might want to consider unrolling your recursion into a loop.

Especially if you are doing multiple levels of recursion (A->B->C->A->B...) you might find that you can extract one of those levels into a loop and save yourself some memory.

1 Comment

+1, good answer. I've seen tree parsing done in a loop. Was much quicker and stack safe.
4

The normal limit, if not much is left on the stack between successive calls, is around 15000-25000 levels deep. 25% of that if you are on IIS 6+.

Most recursive algorhitms can be expressed iteratively.

There are various way to increase allocated stack space, but I'll rather let you find an iterative version first. :)

Comments

1

Other than having a reasonable stack size and making sure you divide and conquer your problem such that you continually work on a smaller problem, not really.

Comments

1

I just thought of tail-recursion, but it turned out, that C# does not support it. However the .Net-Framework seems to support it:

http://blogs.msdn.com/abhinaba/archive/2007/07/27/tail-recursion-on-net.aspx

1 Comment

Notice that the produced IL isn't everything that matters. The JIT does on occasions perform tail-call optimization on the IL code (see Jon's answer).
1

The default stack size for a thread is 1 MB, if you're running under the default CLR. However, other hosts may change that. For example the ASP host changes the default to 256 KB. This means that you may have code that runs perfectly well under VS, but breaks when you deploy it to the real hosting environment.

Fortunately you can specify a stack size, when you create a new thread by using the correct constructor. In my experience it is rarely necessary, but I have seen one case where this was the solution.

You can edit the PE header of the binary itself to change the default size. This is useful if you want to change the size for the main thread. Otherwise I would recommend using the appropriate constructor when creating threads.

Comments

1

I wrote a short article about this here. Basically, I pass an optional parameter called, depth, adding 1 to it each time I go deeper into it. Within the recursive method I check the depth for a value. If it is greater than the value I set, I throw an exception. The value (threshold) would be dependent on your applications needs.

Comments

0

Remember, if you have to ask about system limits, then you are probably doing something horribly wrong.

So, if you think you might get a stack overflow in normal operation then you need to think of a different approach to the problem.

It's not difficult to convert a recursive function into an iterative one, especially as C# has the Generic::Stack collection. Using the Stack type moves the memory used into the program's heap instead of the stack. This gives you the full address range to store the recursive data. If that isn't enough, it's not too difficult to page the data to disk. But I'd seriously consider other solutions if you get to this stage.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.