0

Im curious as why when "if" scope ends, "a" still has the same value even after "c".

void foo()
{
  int* a = nullptr;
  if (true)
  {
    int b = 1;
    a = &b;
  }
  cout<<a<<endl;
  int c = 2;
  cout<<a<<" "<<*a<<endl;
}
0x7fff3ad69878
0x7fff3ad69878 1

I would've thought that stack space for "b" is released, then the init of the next var it would reuse the address 0x7fff3ad69878? Or is this unpredictable behavior and the proper way is to have int b; in the same scope as "a"?

NiceNAS
  • 85
  • 1
  • 6
  • 1
    It is "undefined behavior", anything can happen without _any_ guarantees, [including time travel](https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=633). In the simplest case you can look at the generated assembly code and reason why it looks like this. Compilers can allocate variables wherever they prefer – yeputons Jun 19 '23 at 22:53
  • 1
    *Can a pointer hold reference even after scope ends* **No**, it cannot. It'd be UB to dereference the pointer, or even look at the pointer value (until the pointer is re-assigned). – Eljay Jun 19 '23 at 22:55
  • 1
    The problem is that `b` disappears after execution leaves the statement block. The compiler could optimize out the statement block. For example, if `b` is allocated on the stack, then it may get overwritten when `c` is declared. Accessing variables that disappear is undefined behavior. – Thomas Matthews Jun 19 '23 at 22:55
  • 3
    Enable address sanitizer and observe an error: https://gcc.godbolt.org/z/5axdMrsGT – HolyBlackCat Jun 19 '23 at 22:57
  • [Behavior is different here](https://godbolt.org/z/97r1xnq8K). – PaulMcKenzie Jun 19 '23 at 23:02
  • 1
    @Eljay a pointer can HOLD any address you want it to, even one that is out of scope. It is perfectly ok to LOOK at the address itself which the pointer is holding (ie, to log the address, etc), that is not UB as long as the pointer itself is still valid. You just can't DEREFERENCE the pointer if the address it is holding is invalid, that is the only UB here. IOW, if `a` is a valid pointer holding an invalid address, `cout << a` is perfectly ok (as long as `a` is not a `char*` or `wchar_t*`, that is) while `cout << *a` is UB. – Remy Lebeau Jun 19 '23 at 23:49
  • BTW you should not think of declaring a variable as allocating space for it *in program order*, that's not what happens. Generally all of the stack space a function uses, is allocated in its prologue (with some exceptions). Then it could still have been the case that `b` and `c` shared the same space, but it can also not happen, and most local variables are never in memory anyway. – harold Jun 19 '23 at 23:57
  • @RemyLebeau • `int* p = new int{5}; delete p; if (p) /*...*/` is UB in the `if (p)` before C++14, and implementation defined C++14 and later (I was remembering it was UB still). I wasn't sure (and I'm still not sure) if the same rule applies to automatic storage variables and pointers to them after that object goes out of scope. – Eljay Jun 20 '23 at 00:15

1 Answers1

2

I think it's important to note that you the expected behavior is undefined here, and that what is happening with your compiler and optimization settings cannot be counted on, and are dangerous. You can rationalize what your compiler happens to be doing today, but that could change tomorrow when your compiler is patched, when you link in different code, or when your OS is patched. (Those things may not actually affect this particular behavior, but in general, those things could certainly affect other "undefined behavior".

If you ignore the fact that the if (true) might or might not be optimized out (and might change things), b goes out of scope.. but the compiler could treat that any number of ways. The compiler might put it on the stack, and leave it there, making it possible but not guaranteed to reference it "uncorrupted" for a while. Even when c gets instantiated, the compiler might or might not allocate new space on the stack. In this case, if you print out the addresses of b and c, you might even be able to tell that they are adjacent memory spaces in the stack area. But the most important thing to remember is that you should assume that the compiler is allowed to do anything it wants with b (including do nothing) now that it's out of scope.

All that said, I don't understand your question about "what is the proper way"... what are you really trying to ask?