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.