1

i wrote this block of code in C:

#include <stdio.h>

int* fun() {
    int x = 43;
    int* ret = &x;
    return ret;
}

int main() {
    int* r = fun();
    printf("%d", *r);
    return 0;
}

the original code was:

#include <stdio.h>

int* fun() {
    int x = 43;
    return &x;
}

int main() {
    int* r = fun();
    printf("%d", *r);
    return 0;
}

and i fixed the "Return Local Pointer" Warning by doing

int* ret = &x;

now my question is that in the first example the code prints 43 and i don't understand why ? shouldn't the pointer be a pointer to garbage value after the function "fun" executes and gets removed from the stack because the variable "x" is destroyed and it no longer holds the value 43 ? so why does dereferencing the pointer "r" after "fun" executes results in 43 ? why does this block of memory that held the variable "x" still not removed even after the function "fun" removed from the stack ?

Epsilon
  • 13
  • 2
  • 4
    `43` is as much garbage as `-7652342` or any other number :-) – pmg Apr 21 '21 at 10:44
  • 3
    It's undefined behavior. You're just lucky. Memory is protected on a page basis. It's still there, but might cause a segfault. – Emanuel P Apr 21 '21 at 10:44
  • 3
    Stack memory is never `removed` - it's just made available (unchanged) for whatever it's need for next. – 500 - Internal Server Error Apr 21 '21 at 10:45
  • 2
    Technically, the memory location has been freed but nobody has written anything to it yet, so it still holds the previous value. But this is undefined behavior, you could get a totally different value under other circumstances. – Hellmar Becker Apr 21 '21 at 10:46
  • 2
    [The result may change by turning on optimization](https://wandbox.org/permlink/zfHDkF0Hi5zyAQCG). – MikeCAT Apr 21 '21 at 10:46
  • Does this answer your question? [Return address of local variable in C](https://stackoverflow.com/questions/8743411/return-address-of-local-variable-in-c) – Marco Bonelli Apr 21 '21 at 10:56
  • What if you print it twice? `printf("%d\n", *x); printf("%d\n", *x);`? Number 2 may shock you! – user253751 Apr 21 '21 at 11:07

1 Answers1

0

It is by chance. You are invoking undefined behavior for dereferencing a pointer pointing at an object whose lifetime is ended.

When I entered your code to Compiler Explorer, I got this:

fun:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    $43, -12(%rbp)
        leaq    -12(%rbp), %rax
        movq    %rax, -8(%rbp)
        movq    -8(%rbp), %rax
        popq    %rbp
        ret
.LC0:
        .string "%d"
main:
        pushq   %rbp
        movq    %rsp, %rbp
        subq    $16, %rsp
        movl    $0, %eax
        call    fun
        movq    %rax, -8(%rbp)
        movq    -8(%rbp), %rax
        movl    (%rax), %eax
        movl    %eax, %esi
        movl    $.LC0, %edi
        movl    $0, %eax
        call    printf
        movl    $0, %eax
        leave
        ret

In this illustration, let me assume that %rsp = 0x10000000 just before executing call fun.

call pushes the return address on the stack, so it will subtract 8 from the stack pointer. push also subtract 8 from the stack pointer. Therefore, when movl $43, -12(%rbp) is executed, %rbp is 0x0ffffff0 and the layout on the stack is:

     -12(%rbp) -> 0x0fffffe4  43
                  0x0fffffe8  (the address to return will be written here)
                  0x0fffffec  (the address to return will be written here)
        (%rbp) -> 0x0ffffff0  old base pointer %rbp
                  0x0ffffff4  old base pointer %rbp
                  0x0ffffff8  address to return from fun
                  0x0ffffffc  address to return from fun
                  0x10000000
                  0x10000004
                  0x10000008  (returned address will be stored here in main())
                  0x1000000c  (returned address will be stored here in main())
%rbp in main() -> 0x10000010

After returning from fun(), there are no memory write that destroys the 43 written before reading the value by movl (%rax), %eax. Therefore the value happened to be preserved.

Note again that you are invoking undefined behavior, so the result may differ.

For example, when -O2 optimization option is added, Compiler Explorer gave me this:

fun:
        xorl    %eax, %eax
        ret
.LC0:
        .string "%d"
main:
        subq    $8, %rsp
        xorl    %esi, %esi
        movl    $.LC0, %edi
        xorl    %eax, %eax
        call    printf
        xorl    %eax, %eax
        addq    $8, %rsp
        ret

Now the buggy function fun() is replaced with empty process and the result is changed.

MikeCAT
  • 73,922
  • 11
  • 45
  • 70