title: Understand C++ pointer lifetime / zombie pointers
body: <p>After watching CppCons <a href="https://www.youtube.com/watch?v=7bZQeOGhK84" rel="nofollow noreferrer">Will Your Code Survive the Attack of the Zombie Pointers?</a> I'm a bit confused about pointer lifetime and need some clarification.</p>
  
  <p>First some basic understanding. Please correct me if any comments are wrong:</p>
  
  <pre><code>int* p = new int(1);
  int* q = p;
  // 1) p and q are valid and one can do *p, *q
  delete q;
  // 2) both are invalid and cannot be dereferenced. Also the value of both is unspecified
  q = new int(42); // assume it returns the same memory
  // 3) Both pointers are valid again and can be dereferenced
  </code></pre>
  
  <p>I'm puzzled by 2). Obviously they cannot be dereferenced, but why can't their values be used (e.g. to compare one against another, even unrelated and valid pointer?) This is stated in around <a href="https://youtu.be/7bZQeOGhK84?t=1538" rel="nofollow noreferrer">25:38</a>. I can't find anything about this on <a href="https://en.cppreference.com/w/cpp/language/lifetime" rel="nofollow noreferrer">cppreference</a>, which is where I got 3) from.</p>
  
  <p>Note: The assumption, that the same memory is returned, cannot be generalized as it may or may not happen. For this example it should be taken as granted that it "randomly" returned the same memory as is the case in the video example and (maybe?) required for the below code to break.</p>
  
  <p>The multi-threaded example code from the LIFO list can be put simulated in a single thread as:</p>
  
  <pre><code>Node* top = ... //NodeA allocated before;
  Node* newnodeC = new Node(v); 
  newnodeC-&gt;next = top;
  delete top; top = nullptr;
  // newnodeC-&gt;next is a zombie pointer
  Node* newnodeD = new Node(u); // assume same memory as NodeA is returned
  top = newnodeD;
  if(top == newnodeC-&gt;next) // true
    top = newnodeC;
  // Now top-&gt;next is (still) a zombie pointer
  </code></pre>
  
  <p>This should be valid, unless <code>Node</code> contains nonstatic const members or references according to the rules under </p>
  
  <blockquote>
    <p>If a new object is created at the address that was occupied by another object, then all pointers, references, and the name of the original object will automatically refer to the new object and, once the lifetime of the new object begins, can be used to manipulate the new object, but only if the following conditions are satisfied [which they are]</p>
  </blockquote>
  
  <p>So why is this a zombie pointer and supposedly UB?</p>
  
  <p>Could the (single-threaded) condensed code above be fixed (in case there are const-members) by a <code>newnodeC-&gt;next = std::launder(newnodeC-&gt;next)</code> as of </p>
  
  <blockquote>
    <p>If the conditions listed above are not met, a valid pointer to the new object may still be obtained by applying the pointer optimization barrier std::launder</p>
  </blockquote>
  
  <p>I'd expect this to fix the "zombie pointer" and compilers to not emit instructions for the assignment but simply treat it as an optimization barrier (e.g. when the function is inlined and a const-member is accessed again)</p>
  
  <p>So in summary: I haven't heard of "zombie pointers" before. Am I correct that any pointer to a destroyed/deleted object cannot be used (for reading [the pointers value] and dereferencing [read of the pointee]) unless the pointer is reassigned or the memory reallocated with the same object type recreated there (and without const/reference members)? Can't this be fixed by C++17 <code>std::launder</code> already? (baring multi-threaded issues)</p>
  
  <p>Also: At <code>3)</code> in the first code would <code>if(p==q)</code> even be generally valid? Because from  my understanding of the (second part of the) video it is not valid to read <code>p</code>.</p>
  
  <p>Edit: As an explanation where I'm pretty sure UB happens: Again assume that by pure chance the same memory is returned with the <code>new</code>:</p>
  
  <pre><code>// Global
  struct Node{
    const int value;
  };
  Node* globalPtr = nullptr;
  // In some func
  Node* ptr = new Node{42};
  globalPtr = ptr;
  const int value = ptr-&gt;value;
  foo(value);
  // Possibly on another thread (if multi-threaded assume proper synchronisation so that this scenario happens)
  delete globalPtr;
  globalPtr = new Node{1337}; // Assume same memory returned
  // First thread again (and maybe on serial code too)
  if(ptr == globalPtr)
    foo(ptr-&gt;value);
  else
    foo(globalPtr-&gt;value);
  </code></pre>
  
  <p>According to the video, after <code>delete globalPtr</code> also the <code>ptr</code> is a "zombie pointer" and cannot be used (aka "would be UB"). A sufficiently optimizing compiler can make use of this and assume the pointee was never freed (especially when the delete/new happens on another functon/thread/...) and optimize <code>foo(ptr-&gt;value)</code> to <code>foo(42)</code></p>
  
  <p>Note also the mentioned <a href="http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_260.htm" rel="nofollow noreferrer">Defect Report 260</a>:</p>
  
  <blockquote>
    <p>Where a pointer value becomes indeterminate because the object pointed to has reached the end of its lifetime, all objects whose effective type is a pointer and that point to the same object acquire an indeterminate value. Thus p at point X, and p, q, and r at point Z, can all change their value.</p>
  </blockquote>
  
  <p>I think this is the definitive explanation: After <code>delete globalPtr</code> the value of <code>ptr</code> is also indeterminate. But how can this align with </p>
  
  <blockquote>
    <p>If a new object is created at the address that was occupied by another object, then all pointers [...] of the original object will automatically refer to the new object and[...] can be used to manipulate the new object</p>
  </blockquote>