A Then B Then A

Node* pop() {
    Node* old = head.load(std::memory_order_acquire);
    while (old && !head.compare_exchange_weak(old, old->next,
                      std::memory_order_acquire,
                      std::memory_order_relaxed)) {
        // retry
    }
    return old;
}

The stack starts as head -> A -> B -> C and the allocator hands back freed blocks immediately.

step Thread 1 Thread 2 head after
1 loads head = A, computes old->next = B A
2 descheduled pop() returns A and frees it B
3 pop() returns B and frees it C
4 push() reuses the freed A block, pushing it back A
5 CAS(head, expected A, new B) ?

What is the state after step 5?

A

The CAS fails, because old->next was read before the two pushes and compare-exchange also compares the next field.

B

The CAS succeeds, because head holds the address A again, and sets head to B, a node that was already popped and freed.

C

The CAS fails, because head changed twice between the load and the exchange, and thread 1 retries against the current head.

D

The CAS succeeds and the stack is still correct, since B was reachable from A both before and after.