0

Please consider this simple example:

#include <iostream>

const int CALLS_N = 3;
int * hackPointer;

void test()
{
    static int callCounter = 0;
    int local = callCounter++;
    hackPointer = &local;
}

int main()
{
    for(int i = 0; i < CALLS_N; i++)
    {
        test();

        std::cout << *hackPointer << "(" << hackPointer << ")";
        std::cout << *hackPointer << "(" << hackPointer << ")";

        std::cout << std::endl;
   }
}

The output (VS2010, MinGW without optimization) has the same structure:

0(X) Y(X)
1(X) Y(X)
2(X) Y(X)
...
[CALLS_N](X) Y(X)

where X - some address in memory, Y - some rubbish number.
What is done here is the case of undefined behaviour. However I want to understand why there is such behaviour in current conditions (and it is rather stable for two compilers).
It seems that after test() call first read of hackPointer leads to valid memory, but second successive instant read of it leads to rubbish. Also on any call address of local is the same. I always thought that memory for stack variable is allocated on every function call and is released after return but I can't explain output of the program from this point of view.

Arsenii Fomin
  • 3,120
  • 3
  • 22
  • 42
  • You might want to read [this answer](http://stackoverflow.com/a/6445794/1382251). – barak manos Dec 23 '14 at 11:50
  • 1
    Trying to figure out what happens when undefined behavior occurs is equivalent to a dog chasing its own tail. What happens if the behavior is different if you use differing compiler options? Or a different version of the compiler? – PaulMcKenzie Dec 23 '14 at 11:50
  • 3
    @PaulMcKenzie: IMO understanding how things work and developing intuition is not necessarily pointless as a learning exercise. – NPE Dec 23 '14 at 11:57
  • I concur. The OP knows that this is UB and so we do not need to resort to the usual "omg this is UB you are banned from discussing practical implications" nonsense. C++ is an abstraction but real, physical environments also exist and it can be useful to discuss them. – Lightness Races in Orbit Dec 23 '14 at 11:58
  • Yes, my question is not about undefined behaviour and the "miracle" that the memory stays the same. I understood that many time ago. I want to understand why it is stable for both compilers and what they're doing, so some sort of assembly is appreciated. – Arsenii Fomin Dec 23 '14 at 11:59

2 Answers2

2

"Releasing" automatic storage doesn't make the memory go away, or change the pattern of bits stored there. It just makes it available for reuse, and causes undefined behaviour if you try to access the object that used to be there.

Immediately after returning from the function, the memory occupied by the local probably hasn't been overwritten, so reading it will probably give the value that was assigned within the function.

After calling another function (in this case, operator<<()), the memory is likely to have been reused for a variable within that function, so probably has a different value.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • I understand ""Releasing" automatic storage doesn't make the memory go away, or change the pattern of bits stored there. It just makes it available for reuse" this clearly and got such behaviours many times. I don't understand why address is the same and why such behaviour as on first call to operator<< value is still valid, but on second not is the same on both compilers so is rather stable. What is the reason? Same assembly for operator<< call? – Arsenii Fomin Dec 23 '14 at 11:55
  • 1
    @FominArseniy: Because in the first case, `*hackPointer` has been evaluated literally directly after the call to `test()` and before a call to anything else. In the second case this is not so. (The key is that the arguments to `operator<<` are evaluated _before_ it's called — they must be!) – Lightness Races in Orbit Dec 23 '14 at 11:57
  • @FominArseniy: The address doesn't change because you've stored it in `hackPointer` and don't change it. The value changes because `operator<<` reuses that memory location for its own automatic variables. It's not "valid" before that, it just hasn't been overwritten yet. – Mike Seymour Dec 23 '14 at 11:57
  • I thought that on any call I store in hackPointer new address of local, because it was allocated again. But it's the same. So if I have multithread application with two simultaneous calls of test() from diffrent threads, two different local-s must be allocated. But why in this more simple case local is not allocated on any call again? There is no any optimization on. – Arsenii Fomin Dec 23 '14 at 12:05
  • @FominArseniy _"I thought that on any call I store in hackPointer new address of local, because it was allocated again. ..."_ Try it with a recursive function, and you'll see a different address. – πάντα ῥεῖ Dec 23 '14 at 12:08
  • @FominArseniy: In this case, you're always calling `test` from the same stack frame (that of `main`), so the stack frame for `test` is always created and destroyed in the same place. If you were to call it from another function, you might see a different address. (Assuming a typical implementation, where the "stack" is a contiguous block of memory.) – Mike Seymour Dec 23 '14 at 12:08
  • @πάντα ῥεῖ It's exactly what I'm doing now.) – Arsenii Fomin Dec 23 '14 at 12:13
  • @Mike Seymour: I see now. Thank you. – Arsenii Fomin Dec 23 '14 at 12:14
1

You are quite right that this is undefined behaviour.

That aside, what's happening is that std::cout << *hackPointer involves a function call: operator<<() gets called after the value of *hackPointer has been read. In all likelihood, operator<<() uses its own local variables that end up on the stack where local was, wiping out the latter.

NPE
  • 486,780
  • 108
  • 951
  • 1,012