When a function is called the compiler provides code to prepare stack, save current stack pointer and make space for local (automatic) variables. This commonly known as function prologue. The layout of stack after prologue is more or less:
+-----------------------------+
| Parameters if any |
+-----------------------------+
| Return address |
+-----------------------------+
| copy of ESP (stack pointer) |
+-----------------------------+
| Local variables begin here |
+-----------------------------+
| ... |
Now if you have 3 functions that will develop the same layout:
foo() bar() foobar()
+----------------+ +----------------+ +----------------+
| Return address | | Return address | | Return address |
+----------------+ +----------------+ +----------------+
| ESP | | ESP | | ESP |
+----------------+ +----------------+ +----------------+
| int a | | int a | | int a |
+----------------+ +----------------+ +----------------+
| ... | | ... | | ... |
If you have got the address of the variable a
in the first function foo()
the same address will be reused when you will call bar()
and foobar()
. Accessing a
after the call you will find the last value written there by by foobar()
.
If you change your functions this way:
#include<stdio.h>
int* foo() {
int a = 5;
return &a;
}
int* bar() {
int a;
int b = 7;
return &b;
}
int* foobar() {
int a;
int b;
int c = 97;
return &c;
}
int main() {
int* p1 = foo();
int* p2 = bar();
int* p3 = foobar();
printf("%d %d %d", *p1, *p2, *p3);
return 0;
}
Surprisingly you will read all values. The situation is now:
foo() bar() foobar()
+----------------+ +----------------+ +----------------+
| Return address | | Return address | | Return address |
+----------------+ +----------------+ +----------------+
| ESP | | ESP | | ESP |
+----------------+ +----------------+ +----------------+
| int a | | int a | | int a |
+----------------+ +----------------+ +----------------+
| ... | | int b | | int b |
+----------------+ +----------------+ +----------------+
| ... | | ... | | int c |
+----------------+ +----------------+ +----------------+
| ... | | ... | | ... |
BTW this go under the big family of undefined behaviors, and most important is an error. The lifespan of an automatic variable is limited to its scope, and must not be used outside.
Anyway the behavior of values on the stack is generally stable because the memory manager preserves the data of stack pages (that's why doesn't make sense to use uninitialized local variables as random values), but in future architectural designs the MM can discard unused memory and don't save it making effectively undefined the contents of these memory locations.