7

The question is about how linux handle the stack. Why is not deterministic when I get segmentation-fault running this code?

#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>

void step(int n) {
    printf("#%d\n", n);
    step(n + 1);
}

int main() {
    step(1);
    return 0;
}
  • what happens if you add `fflush(stdout)` after `printf()` ? – Mike Nakis Jul 31 '17 at 14:29
  • Without any code changing the default buffering mode of `stdout`, this flush should be implicit with the `\n`... –  Jul 31 '17 at 14:30
  • 1
    But this could still point in the right direction, as there are probably other buffers involved as well ... How much do the numbers vary? Just by one? –  Jul 31 '17 at 14:32
  • Try to compile with the -O2 flag and run your code. You can read about recursiv on internet. This code StackOverflow. – pierreafranck Jul 31 '17 at 14:33
  • @pierreafranck the question wasn't why it's crashing.... –  Jul 31 '17 at 14:33
  • Let me make sure I understand - your code prints `1 2 3 4 5 ...` until some point when your program segfaults, and the last number varies? – John Bode Jul 31 '17 at 14:35
  • yes but the number doesn't varies on my linux. it depends of OS so he had to learn about recursive – pierreafranck Jul 31 '17 at 14:35
  • 1
    Ah, now I see what's going on - the exact number of stack frames your program craps out on varies. On my runs, I get `261533`, `261477`, `261581`, etc. I thought you were seeing a fixed sequence of numbers, and the last element of the sequence was something random. IMO, this behavior is not unexpected on a system that's running multiple processes. – John Bode Jul 31 '17 at 14:42
  • @FelixPalmen A [`'\n'` is not necessarily](https://stackoverflow.com/q/39536212/2410359) a flush. – chux - Reinstate Monica Jul 31 '17 at 14:44
  • 2
    The function is infinitely recursive. If actually implemented by the function calling itself forever, the depth of calls possible depends on available memory resources, so will be affected by other programs running on the system and their memory usage. Of course, if the compiler is aggressive enough, it may well turn the recursive call into a loop, in which case memory is not a concern (but the overflow of an `int` causing undefined behaviour is, even if the results are still unpredictable). – Peter Jul 31 '17 at 14:44
  • 1
    @JohnBode I'd be interested in an explanation as well. Isn't every process given the same amount of stack space? –  Jul 31 '17 at 14:45
  • C does not _specify_ a seg-fault as the result of any code. So if code seg-faults, it is due to undefined behavior (UB). UB may vary. – chux - Reinstate Monica Jul 31 '17 at 14:47
  • @chux of course, but on linux using glibc, the "missing" `fflush()` isn't the culprit, that's what I wanted to express. -- I think the question is more about Linux than about C. –  Jul 31 '17 at 14:47
  • @FelixPalmen: In virtual space, sure (probably - I'm a dumb applications code monkey, not a system-level expert). But when virtual space gets mapped to physical memory, there are going to be conflicts between processes, so I would not be surprised if the segfaulting frame isn't 100% consistent from run to run. – John Bode Jul 31 '17 at 14:52
  • In Linux, the exact size and address of the stack for the initial thread in each process varies; see [ASLR](https://en.wikipedia.org/wiki/Address_space_layout_randomization) and especially [exec shield](https://en.wikipedia.org/wiki/Exec_Shield). (Hm, should have made this an answer. Welp, too late now.) – Nominal Animal Jul 31 '17 at 15:43

2 Answers2

2

Looks like non-deterministic results is a consequence of environment randomization policy that kernel uses when starts new program. Lets try next code:

#include <stdio.h>
#include <stdint.h>
#include <unistd.h>

int main(int argc, char **argv) {
    char c;
    uintptr_t addr = (uintptr_t)&c;
    unsigned pagesize = (unsigned)sysconf(_SC_PAGE_SIZE);
    printf("in-page offset: %u\n", (unsigned)(addr % pagesize));
    return 0;
}

On my 64-bit Linux it gives next output:

$ ./a.out
in-page offset: 3247
$ ./a.out
in-page offset: 2063
$ ./a.out
in-page offset: 863
$ ./a.out
in-page offset: 1871

Each time c gets new offset within its stack page, and knowing that kernel always allocates discrete number of pages for stack - it is easy to see that each time program has slightly different amount of allocated stack. Thus program described in the question has non-constant amount of stack for its frames per each invokation.

Being frankly I'm not sure if it is kernel who tunes initial value for stack pointer or maybe it is some trick from dynamic linker. Anyway - user code will run in randomized environment each time.

Sergio
  • 8,099
  • 2
  • 26
  • 52
1

Because a stack overflow is Undefined Behaviour. An implementation is free to test that it does not occur, in which case the program should end with error when the stack is full. But the environment could also provide a stack with a size depending on the free memory. Or more probably you could get various memory overwriting problem in interaction with the io system which could be non deterministics. Or... (essentially UB means that anything could happen).

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • 1
    `But the environment could also provide a stack with a size depending on the free memory.`...can you please elaborate on this? I always thought the stack size is fixed.... – Sourav Ghosh Jul 31 '17 at 14:56
  • @SouravGhosh: The size of the stack is generally a build time (link time) parameter, but I never see this mandated in the standard. So an implementation should be allowed to let the stack grow when required, provided there is enough available system memory - but I must admit that I know no implementation doing that :-) – Serge Ballesta Jul 31 '17 at 15:10