2

I am working on a simulation problem written in c, the main part of my program is a recursive function. when the recursive depth reaches approximately 500000 it seems stack overflow occurs.

Q1 : I want to know that is this normal?

Q2 : in general how many recursive function calls causes stack overflow?

Q3 : in the code below, removing local variable neighbor can prevent from stack overflow?

my code:

/*
 * recursive function to form Wolff Cluster(= WC)
 */
void grow_Wolff_cluster(lattic* l, Wolff* wolff, site *seed){

    /*a neighbor of site seed*/
    site* neighbor;

    /*go through all neighbors of seed*/
    for (int i = 0 ; i < neighbors ; ++i) {


        neighbor = seed->neighbors[i];

        /*add to WC according to the Wolff Algorithm*/
        if(neighbor->spin == seed->spin && neighbor->WC == -1 && ((double)rand() / RAND_MAX) < add_probability)
        {
            wolff->Wolff_cluster[wolff->WC_pos] = neighbor;
            wolff->WC_pos++;                  // the number of sites that is added to WC
            neighbor->WC = 1;          // for avoiding of multiple addition of site
            neighbor->X = 0;


            ///controller_site_added_to_WC();


            /*continue growing Wolff cluster(recursion)*/
            grow_Wolff_cluster(l, wolff, neighbor);
        }
    }
}
mehrdad
  • 161
  • 2
  • 11
  • How many? Until the stack is used up. Under Windows the system can add additional memory, so... it may take really a lot. In your case, you don't have many local variables (only a pointer), so it's just this pointer and the function frame - really too little. – Constantine Georgiou Jan 10 '18 at 00:32
  • is it normal to have stack overflow after 500000 recursive calls ?on CentOs 7 – mehrdad Jan 10 '18 at 00:34
  • This looks [awfully familiar](https://stackoverflow.com/questions/48173404/how-to-know-a-c-programs-stack-overflows) ... 500k calls would do it, but it completely depends on your system's stack size and what you're pushing there, take a look [here](https://stackoverflow.com/questions/7535994/how-do-i-find-the-maximum-stack-size) – yano Jan 10 '18 at 00:35
  • If you ever need more than 100 levels of recursion, then recursion isn't the right solution to your problem (not counting "tail" recursion which most decent compilers will convert to iteration for you). – Lee Daniel Crocker Jan 10 '18 at 00:45
  • 1
    The default stack size on a Linux machine is 8 MiB. If you need more than about 16 bytes of data per call (including return addresses etc), you'd be running into problems at 500,000 calls deep (but not at 500,000 calls if you've returned from 499,900 of them already). It is the number of levels of recursive call that cause trouble - stack overflows - and not the total number of calls. – Jonathan Leffler Jan 10 '18 at 00:47
  • Possible duplicate of [how to know a c program's stack overflows?](https://stackoverflow.com/questions/48173404/how-to-know-a-c-programs-stack-overflows) Editing your original post with updated information, or questions would be sufficient to update the active questions list and draw people to visit. That is always preferable to asking the same question twice. – ryyker Jan 10 '18 at 00:54

5 Answers5

10

I want to know that is this normal?

Yes. There's only so much stack size.

In the code below, removing local variable neighbor can prevent from stack overflow?

No. Even with no variables and no return values the function calls themselves must be stored in the stack so the stack can eventually be unwound.

For example...

void recurse() {
    recurse();
}

int main (void)
{
    recurse();
}

This still overflows the stack.

$ ./test
ASAN:DEADLYSIGNAL
=================================================================
==94371==ERROR: AddressSanitizer: stack-overflow on address 0x7ffee7f80ff8 (pc 0x00010747ff14 bp 0x7ffee7f81000 sp 0x7ffee7f81000 T0)
    #0 0x10747ff13 in recurse (/Users/schwern/tmp/./test+0x100000f13)

SUMMARY: AddressSanitizer: stack-overflow (/Users/schwern/tmp/./test+0x100000f13) in recurse
==94371==ABORTING
Abort trap: 6

In general how many recursive function calls causes stack overflow?

That depends on your environment and function calls. Here on OS X 10.13 I'm limited to 8192K by default.

$ ulimit -s
8192

This simple example with clang -g can recurse 261976 times. With -O3 I can't get it to overflow, I suspect compiler optimizations have eliminated my simple recursion.

#include <stdio.h>

void recurse() {
    puts("Recurse");
    recurse();
}

int main (void)
{
    recurse();
}

Add an integer argument and it's 261933 times.

#include <stdio.h>

void recurse(int cnt) {
    printf("Recurse %d\n", cnt);
    recurse(++cnt);
}

int main (void)
{
    recurse(1);
}

Add a double argument, now it's 174622 times.

#include <stdio.h>

void recurse(int cnt, double foo) {
    printf("Recurse %d %f\n", cnt, foo);
    recurse(++cnt, foo);
}

int main (void)
{
    recurse(1, 2.3);
}

Add some stack variables and it's 104773 times.

#include <stdio.h>

void recurse(int cnt, double foo) {
    double this = 42.0;
    double that = 41.0;
    double other = 40.0;
    double thing = 39.0;
    printf("Recurse %d %f %f %f %f %f\n", cnt, foo, this, that, other, thing);
    recurse(++cnt, foo);
}

int main (void)
{
    recurse(1, 2.3);
}

And so on. But I can increase my stack size in this shell and get twice the calls.

$ ./test 2> /dev/null | wc -l
174622
$ ulimit -s 16384
$ ./test 2> /dev/null | wc -l
349385

I have a hard upper limit to how big I can make the stack of 65,532K or 64M.

$ ulimit -Hs
65532
Schwern
  • 153,029
  • 25
  • 195
  • 336
  • 1
    Note: My results are with `-g`. With `-O3` I can't get the stack to overflow. I suspect the compiler has optimized away my simple recursion. – Schwern Jan 10 '18 at 01:06
3

A stack overflow isn’t defined by the C standard, but by the implementation. The C standard defines a language with unlimited stack space (among other resources) but does have a section about how implementations are allowed to impose limits.

Usually it’s the operating system that actually first creates the error. The OS doesn’t care about how many calls you make, but about the total size of the stack. The stack is composed of stack frames, one for each function call. Usually a stack frame consists of some combination of the following five things (as an approximation; details can vary a lot between systems):

  1. The parameters to the function call (probably not actually here, in this case; they’re probably in registers, although this doesn’t actually buy anything with recursion).
  2. The return address of the function call (in this case, the address of the ++i instruction in the for loop).
  3. The base pointer where the previous stack frame starts
  4. Local variables (at least those that don’t go in registers)
  5. Any registers the caller wants to save when it makes a new function call, so the called function doesn’t overwrite them (some registers may be saved by the caller instead, but it doesn’t particularly matter for stack size analysis). This is why passing parameters in registers doesn’t help much in this case; they’ll end up on the stack sooner or later.

Because some of these (specifically, 1., 4., and 5.) can vary in size by a lot, it can be difficult to estimate how big an average stack frame is, although it’s easier in this case because of the recursion. Different systems also have different stack sizes; it currently looks like by default I can have 8 MiB for a stack, but an embedded system would probably have a lot less.

This also explains why removing a local variable gives you more available function calls; you reduced the size of each of the 500,000 stack frames.


If you want to increase the amount of stack space available, look into the setrlimit(2) function (on Linux like the OP; it may be different on other systems). First, though, you might want to try debugging and refactoring to make sure you need all that stack space.

Daniel H
  • 7,223
  • 2
  • 26
  • 41
  • This is nicely written, but it does not answer the numbered questions in OP’s question or explain the mistaken concepts inherent in them. E.g., Q1: Yes, it is normal for many calls to overflow the stack. Q2: The number of calls it takes to overflow the stack depends on how much space each function needs. Q3: Removing `neighbor` could reduce the space one call uses and increase the number of calls that could be made before overflow occurs, but it will not prevent it. None of these are addressed directly in this answer. – Eric Postpischil Jan 10 '18 at 01:03
  • @EricPostpischil I don’t have them nicely numbered, but I did address all of them. Respectively, by answers are “It’s normal for a lot of calls to overwhelm the stack, but I don’t know if 500,000 is a reasonable number in your environment”, “It’s not the number of calls that’s relevant but the frame size”, and “Removing `neighbor` reduces frame size so makes the number of calls needed to get an overflow higher”. – Daniel H Jan 10 '18 at 01:17
2
  1. Yes and no - if you come across a stack overflow in your code, it could mean a few things

    • Your algorithm is not implemented in a way that respects the amount of memory on the stack you have been given. You may adjust this amount to suit the needs of the algorithm.

      If this is the case, it's more common to change the algorithm to more efficiently utilize the stack, rather than add more memory. Converting a recursive function to an iterative one, for example, saves a lot of precious memory.

    • It's a bug trying to eat all your RAM. You forgot a base case in the recursion or mistakenly called the same function. We've all done it at least 2 times.

  2. It's not necessarily how many calls cause an overflow - it's dependent upon how much memory each individual call takes up on a stack frame. Each function call uses up stack memory until the call returns. Stack memory is statically allocated -- you can't change it at runtime (in a sane world). It's a last-in-first-out (LIFO) data structure behind the scenes.

  3. It's not preventing it, it's just changing how many calls to grow_Wolff_cluster it takes to overflow the stack memory. On a 32-bit system, removing neighbor from the function costs a call to grow_Wolff_cluster 4 bytes less. It adds up quickly when you multiply that in the hundreds of thousands.

I suggest you learn more about how stacks work for you. Here's a good resource over on the software engineering stack exchange. And another here on stack overflow (zing!)

Austin Brunkhorst
  • 20,704
  • 6
  • 47
  • 61
  • 1
    thank you for your answer, my code is fine, but the algorithm that I use must be implemented with recursive function, and for small recursive depth i have no problem and my results is good. – mehrdad Jan 10 '18 at 00:45
  • 1
    Since stack size is a settable value in most environments, your answer to 1 is not convincing. i.e. Stack overflow does not necessarily mean _wrong_ code. It just means the stack size needs to be adjusted up for that particular application. – ryyker Jan 10 '18 at 00:48
  • 1
    @ryyker I can see that interpretation. I'll update my wording a bit. – Austin Brunkhorst Jan 10 '18 at 00:49
  • Regarding 1, systems often limit stack size more than actually necessary. You might have an entirely good algorithm that simply requires recursion, and re-implementing it as a loop with a stack structure on the heap would let it work on much larger amounts of data. – Daniel H Jan 10 '18 at 00:50
  • @ryyker This is kind of like saying `malloc` failing because you ran out of memory means you just need to add more RAM. You're correct that it's not necessarily *wrong* to overflow the stack or run out of RAM, but your first response should be to optimize your use of finite resources rather than just add more. – Schwern Jan 10 '18 at 00:51
  • @Schwern True to some extent, but (at least on Linux) you get “unlimited” heap by default (that is, until all the RAM on the system is actually being used and Linux kills something), but only 8 MiB stack. The stack is a finite resource no matter what, but it is an *artificially limited* resource by default. (Here I mean “artificial” as saying that it doesn’t represent a limit the hardware has; I don’t know enough about the tradeoffs to say that it’s a bad idea to put some limit in place, although a default of 8 MiB regardless of physical memory does seem small.) – Daniel H Jan 10 '18 at 00:55
  • @DanielH You're correct that it is artificial. It is also one that you, as the developer, do not control. And a normal user can only make it so large. So again, "just increase the stack" is a poor first response; it burdens the user with special configuration requirements. – Schwern Jan 10 '18 at 00:58
  • @Schwern: Developers can control stack size. E.g., with Apple’s ld, specify “-stack_size *hexadecimal number*”. (If using Xcode, add “-Wl,-stack_size,*hexadecimal number*” to Other Linker Flags.) – Eric Postpischil Jan 10 '18 at 01:10
  • @Schwern - Analogy is weak :), but I get, and do not fully disagree with the point I you are conveying. Anecdotally, the default stack size on the environment I use is 250 kBytes. A recent application I wrote for use on Windows uses variable length arrays to accommodate sizes that are only known at run-time. In this case, my software which was arguably not perfect, but completely adequate for its purpose caused a stack overflow. Given the nature of the application (a Windows app) I simply upped the stack size available to the app. (10 seconds) verses debugging and editing code. (> 10 secs) – ryyker Jan 10 '18 at 01:11
  • @ryyker that seems more of a business anecdote, not one that is holistically beneficial to the user. – Austin Brunkhorst Jan 10 '18 at 01:14
  • @Schwern I don’t know if this is typical or Red Hat–specific, but on my Fedora, CentOS 6, and, most relevantly for the question, CentOS 7 systems, the default hard limit is “unlimited”, which is the default soft limit for heap size. Obviously a sysadmin can change that and might limit stack more than heap, but by default you can go as far as you want. – Daniel H Jan 10 '18 at 01:15
  • @AustinBrunkhorst - it was, directed as a response to Schwern. Business case or no, sometimes it is not a wise to spend excess time on a lab tool that works perfectly fine. – ryyker Jan 10 '18 at 01:18
  • @ryyker no I understand. My experiences have been the same, it's just a dangerous mindset to adhere to. Are you more comfortable with my updated answer? – Austin Brunkhorst Jan 10 '18 at 01:20
  • 1
    Yes, the content of your answer is good, but the comments under your answer are spectacular :) – ryyker Jan 10 '18 at 01:26
  • Cheers! We might as well write a book about the stack, here in this comment thread. – Austin Brunkhorst Jan 10 '18 at 01:28
0

For each time a function recurs, your program takes more memory on the stack, the memory it takes for each function depends upon the function and variables within it. The number of recursions that can be done of a function is entirely dependant upon your system.

There is no general number of recursions that will cause stack overflow.

Removing the variable 'neighbour' will allow for the function to recur further as each recursion takes less memory, but it will still eventually cause stack overflow.

Jonathan Woollett-light
  • 2,813
  • 5
  • 30
  • 58
0

This is a simple c# function that will show you how many iteration your computer can take before stack overflow (as a reference, I have run up to 10478):

    private void button3_Click(object sender, EventArgs e)
    {
        Int32 lngMax = 0;
        StackIt(ref lngMax);
    }

    private void StackIt(ref Int32 plngMax, Int32 plngStack = 0)
    {
        if (plngStack > plngMax)
        {
            plngMax = plngStack;
            Console.WriteLine(plngMax.ToString());
        }

        plngStack++;
        StackIt(ref plngMax, plngStack);
    }

in this simple case, the condition check: "if (plngStack > plngMax)" could be removed, but if you got a real recursive function, this check will help you localize the problem.