Using Recursion in C#

24,404

Solution 1

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 be 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.

Solution 2

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.

Solution 3

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.

Solution 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.

Solution 5

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. :)

Share:
24,404
Ted Smith
Author by

Ted Smith

Updated on January 20, 2020

Comments

  • Ted Smith
    Ted Smith over 4 years

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

  • Jon Skeet
    Jon Skeet about 15 years
    @leppie: Exactly the sort of rules I wasn't remembering :) I'll see if I can dig up the blog post on it.
  • AwesomeTown
    AwesomeTown about 15 years
  • AwesomeTown
    AwesomeTown about 15 years
    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).
  • Konrad Rudolph
    Konrad Rudolph about 15 years
    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).
  • batbrat
    batbrat about 15 years
    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.
  • batbrat
    batbrat about 15 years
    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.
  • batbrat
    batbrat about 15 years
  • batbrat
    batbrat about 15 years
    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.
  • batbrat
    batbrat about 15 years
    So, unless n=1 is reached by the above code, it will recurse infinitely and cause a stack overflow
  • leppie
    leppie about 15 years
    @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.
  • Dead account
    Dead account about 15 years
    +1, good answer. I've seen tree parsing done in a loop. Was much quicker and stack safe.
  • MbaiMburu
    MbaiMburu about 15 years
    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.
  • Michael Meadows
    Michael Meadows about 15 years
    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.
  • Michael Meadows
    Michael Meadows about 15 years
    Also, if it's not a logical limit, then it's really just arbitrary and could result in results of variable preciseness.
  • Michael Meadows
    Michael Meadows about 15 years
    @Jon, I agree with the last paragraph in your answer. If the logical limit of your recursion is not "knowable" then recursion is very likely the wrong route to take.
  • leppie
    leppie over 13 years
    You can also specify an arbitrary stack size when creating a thread. See stackoverflow.com/questions/316938/…