2

Under Linux start with

ulimit -s 1024

to limit the stack size.

First of all a program which works:

#include <stdio.h>

static int calc(int c,int *array)
{
    if (array!=NULL) {
        if (c<=0) {
            array[0]=1;
            return 1;
        }
        calc(c-1,array);
        array[c]=array[c-1]+3;
        return array[c];
    } else {
        int a[2500+c];
        calc(c-1,a);
        a[c]=a[c-1]+3;
        return a[c];
    }
}

int main()
{
    int result;
    result = calc(1000,NULL);
    printf("result = %d\n",result);
}

Now if I change int a[2500+c]; to int a[2500]; then the program crashes with a stack overflow (segmentation fault).

I tried this with

  • gcc-4.4.7 -O0, gcc-4.4.7 -O2, gcc-4.4.7 -O3
  • gcc-4.8.4 -O0, gcc-4.8.4 -O2, gcc-4.8.4 -O3
  • gcc-4.9.3 -O0, gcc-4.9.3 -O2, gcc-4.9.3 -O3

If I use

ulimit -s 1024

then the version with int a[2500]; crashes, whereas the version with int a[2500+c]; works.

Why does the version of the program which uses a variable length array (int a[2500+c];) consume less stack space than the version which uses a fixed length array (int a[2500];) ?

Ingo Blackman
  • 991
  • 8
  • 13
  • Note: The program will also work for both cases if you (for example) use `calc(20,NULL);` in main. The 2nd version (with the fixed length array) just uses **lots** more stack. – Ingo Blackman Dec 05 '16 at 19:48
  • Both versions run without error for me. – John Bollinger Dec 05 '16 at 19:48
  • John: Which compiler ? – Ingo Blackman Dec 05 '16 at 19:49
  • I can reproduce this with gcc 4.8.4 `-O0`. On `-O3` both versions work fine. The reason is that the compiler unconditionally allocates the `int a[2500];` with `-O0` in the function prologue, without checking `array != NULL` – EOF Dec 05 '16 at 19:51
  • I'm using gcc 4.4.7 on Linux, with default options. And I've tried eliciting a stack overflow by increasing the argument to the initial `calc()` call and by increasing the size of array `a`, but no luck after increasing both by a factor of 100 (at the same time). – John Bollinger Dec 05 '16 at 19:52
  • 4
    perhaps if you say `a[2500]` the compiler will try and put the array on the stack, but if you say `a[2500+c]` it will use the heap, since the size of a isn't known at compile time. – bruceg Dec 05 '16 at 19:53
  • One moment please, I have to try out with gcc 4.4 maybe only the more recent gcc versions have a problem... – Ingo Blackman Dec 05 '16 at 19:54
  • @BLUEPIXY: `if (c<=0) { array[0]=1; return 1; }` looks terminatey to me. – EOF Dec 05 '16 at 19:55
  • @BLUEPIXY: Yes there is (albeit not perfect): `if (c<=0)...` – Ingo Blackman Dec 05 '16 at 19:55
  • @BLUEPIXY, the fact that the function in fact terminates contradicts your analysis. – John Bollinger Dec 05 '16 at 19:55
  • John: For me it still crashes with gcc-4.4 and Ubuntu 14.04. What OS do you use ? Maybe there is a difference ? Note: If you use `ulimit -s 1024` before you run the program with `int a[2500];` does it still not crash ? – Ingo Blackman Dec 05 '16 at 19:59
  • FYI, this is a follow-up to [this question](http://stackoverflow.com/questions/40974056/when-exactly-is-stack-allocated). – dbush Dec 05 '16 at 19:59
  • @dbush: yes: I did not get an answer and now tried to condense this to the part which is really baffling: Why does the `int a[2500];` version use **more** stack than the `int a[2500+c];` version ? – Ingo Blackman Dec 05 '16 at 20:03
  • @IngoBlackman, I'm using CentOS 6.8. I can reproduce the behavior you describe if I set `ulimit -s 1024`, including the variation between the two versions of your code. – John Bollinger Dec 05 '16 at 20:06
  • @John: Thank you for trying out. I guess CentOS has a much higher stack usage limit per default. So the problem is not triggered there (Ubuntu 14.04 seems to use 8MiB per default and this seems to be too small already...) – Ingo Blackman Dec 05 '16 at 20:07
  • @EOF: I just tried with gcc-4.8.4 with -O3; if I use `int a[2500];` I still get a crash. I agree: The compiler seems to allocate the space for `int a[2500]` without checking for `array!=NULL`. But why ? Why is space allocated for an array which is out of scope ? – Ingo Blackman Dec 05 '16 at 20:12
  • As was mentioned in the previous question, "why" is an implementation detail of the compiler. The standard doesn't dictate how the stack is laid out (or if there even is a stack). You're better off using `malloc` in this case instead of using a local array. That will give you more deterministic behavior. – dbush Dec 05 '16 at 20:15
  • @dbush: Forget about the stack. Why is the compiler allocating space (doesn't matter if it's stack or whatever) for a variable which is out of scope ? Do I simply have to assume that the compiler might allocate space for ALL variables in the program (even if they are out of scope) ? – Ingo Blackman Dec 05 '16 at 20:19
  • I can't speak for the gcc developers, but I looked at the generated code, and there is one efficiency advantage when it allocates as many auto variables as possible upon function entry: the variables can be referenced as fixed offsets from the frame pointer. You'll see instructions like `mov eax, DWORD PTR [rbp-10032+rax*4]` for `array[c]`, whereas for `a[c]` the compiler first has to load its start address, `mov rax, QWORD PTR [rbp-32]` then do `mov eax, DWORD PTR [rax+rdx*4]` – Mark Plotnick Dec 06 '16 at 21:30

1 Answers1

4

As was mentioned in the comments of your previous question, "why" is an implementation detail of the compiler. The standard doesn't dictate how the stack is laid out (or if there even is a stack). As you've seen different versions of the same compiler, or the same version with different optimization settings, can alter how the compiler does things under the hood.

That being said, the compiler probably allocates stack space for all variables that are declared anywhere in the function that are not VLAs when the function is entered. It's probably more efficient in some way that's opaque to the developer. With a VLA, the size isn't known until runtime, so it's allocated differently.

You're better off using malloc in this case instead of using a local array. That will give you more deterministic behavior.

....
} else {
    int *a = malloc(c * sizeof(int));
    if (a == NULL) {
        perror("malloc failed");
        exit(1);
    }
    calc(c-1,a);
    a[c]=a[c-1]+3;
    int rval = a[c];
    free(a);
    return rval;
}
Community
  • 1
  • 1
dbush
  • 205,898
  • 23
  • 218
  • 273
  • Your answer basically boils down to: The compiler might allocate space (it does not matter if it's stack) for any variable mentioned somewhere in the program, even if the variable in question is out of scope. How is that an answer ? You basically say there are no guarantees whatsoever how much space the compiler might allocate for variables out of scope for code which is never run... – Ingo Blackman Dec 05 '16 at 20:28
  • @IngoBlackman Precisely. There are no guarantees, at least not within the scope of a given function. – dbush Dec 05 '16 at 20:31
  • @4386427: If I write something like `int *p; { int a[10]; p=&(a[0]); } *p=5;` this is undefined behavior, because `a` is out of scope when I access it via `*p`. It's not allowed to access `a` anymore because it is not "valid" at the time I access it via `*p`. So why should there still be space allocated for `a` if the standard tells me accessing `a` is undefined behavior ? – Ingo Blackman Dec 05 '16 at 20:32
  • @dbush: So if I write `int myfunc() { a[1000000000]; }` and I never call `myfunc()` then the compiler might still allocate space for `a`, because as you say "there are no guarantees". That is not exactly convincing. – Ingo Blackman Dec 05 '16 at 20:34
  • @4386426 because the program in my question crashes; it would not crash if there would be no space allocated for `a`. – Ingo Blackman Dec 05 '16 at 20:35
  • @IngoBlackman No guarantees within the scope of a given function call. The compiler knows at compile time what variables are used in a function, so when the function is called it sets aside memory for those variables. As I said, setting aside the memory at the start of the function call is probably more efficient in some opaque way than allocating and deallocating them as local scopes are entered / exited. – dbush Dec 05 '16 at 20:37
  • @dbush: And how do you know there are guarantees for variables from functions which are **not** called ? After all the compiler knows about these other functions so why not just allocate space for all local variables, not just for the function which is currently executed. According to your reasoning there are "no guarantees". So how do you come to the conclusion that there are guarantees for local variables that are declared in other functions ? – Ingo Blackman Dec 05 '16 at 20:39
  • @dbush: "... the compiler knows at compile time what variables are used in a function." Well that's plainly wrong in the example I gave. Because it is **not** known at compile time if `int a[];` is used. It might never get into scope... – Ingo Blackman Dec 05 '16 at 20:44
  • @IngoBlackman Bad wording on my part. It should have been "what variables exist in a function". – dbush Dec 05 '16 at 20:45
  • @dbush: And the compiler knows "what variables exist" in the program. So why doesn't it allocate space for all (local) variables which exist in the program ? I argue that the compiler should allocate space for variables which are in scope, whereas you argue that is just an implementation detail and the compiler might also allocate space for all variables which exist. Why do you limit your reasoning to variables "within a function" ? Why not all (local) variables which exist ? – Ingo Blackman Dec 05 '16 at 20:49
  • @4386427: But that's saying I have to assume a compiler might allocate space for **any** variable which exists somewhere in the program. I do not believe that is a reasonable assumption... – Ingo Blackman Dec 05 '16 at 20:50
  • @IngoBlackman Because if a function is called multiple times (as in your program), there is a copy of those variables for *each* invocation of the function. So how would the compiler know how many to set aside? – dbush Dec 05 '16 at 20:52
  • @dbush: And now we are back to square one: Why should the compiler allocate space for a variable simply because it exists, even if it is out of scope ? To answer your question: The compiler could simply multiply the required space for all variables by 100 and thus limit the maximum recursion depth to 100. That's not really a problem because there anyway is a practical limit on the recursion depth. Note: The compiler obviously is able to generate a **different** strategy if I use `int a[2500+c];`, so why is the same strategy not used if I use `int a[2500];` – Ingo Blackman Dec 05 '16 at 20:57
  • @4386427: Out of interest what compiler do you use ? I am still trying to figure out if that's only gcc related. What OS ? – Ingo Blackman Dec 05 '16 at 21:06
  • @4386427: I actually checked the assembly. That's how this question originally arose: It turns out the compiler allocates stack at the function entry when i use `int a[2500];` but it does **not** allocate stack at the function entry when I use `int a[2500+c];` and that's the part which really got me wondering what's going on... – Ingo Blackman Dec 05 '16 at 21:09
  • @4386427: Hmmmmm have you also tried with the `uname -s 1024` ? It turns out not every (linux) system has a strict limit on stack usage. So you must ensure a strict stack limit by first calling `uname -s 1024` – Ingo Blackman Dec 05 '16 at 21:11