The rule is not "using a variable after it has passed out of scope gives garbage output". It is that using a reference to a variable that has gone out of scope is undefined behaviour according to all C++ standards.
Undefined behaviour means the C++ standard provides no guarantee whatsoever about what happens. A consequence is that, when behaviour is undefined, any actual observable result is permitted. Garbage output is only one possible observable result.
That means any explanation of the behaviour you're seeing will be specific to your implementation (compiler, your chosen optimisation or debugging settings, etc, memory management by your host system, ....). The behaviour may also vary over time, since - when behaviour is undefined - there is no requirement that any particular behaviour occurs consistently.
As a generic explanation, in your specific case, it is probably related to how your compiler manages usage of machine registers by your program. The variable i
in foo()
may be stored in a register, then that register may not be cleared immediately, so the value 4
is retrieved from it in the first cout << j << endl
statement. The working of output streams (implementation of operator<<()
or endl
) may then use the same register internally - since there is absolutely no way that C++ code with well-defined behaviour can access those registers directly - and therefore overwrite it.
But that's just a guess. As I said, it depends on the implementation - that's why I used the word "may" so liberally in the preceding paragraph. When behaviour is undefined (by the standard) then a compiler is permitted to do anything. You could see a completely different behaviour by tweaking optimisations settings or next time you update your compiler. Different compilers may do things completely differently as well.