44 / 163 · C++11 · 9 min
Dangling and uninitialized pointers
An uninitialized pointer has no reliable pointer value; a dangling pointer once pointed at a valid object, but the target has already been destroyed or invalidated. Initializing to nullptr only fixes the starting state and cannot track object lifetime; the fundamental approach is to constrain borrow scope and arrange ownership correctly.
In this lesson
The two problems occur at different stages
Inside a block, int* p; does not give p a valid initial value. In the standard versions this tutorial covers, reading this indeterminate pointer value can already be undefined behavior; you need not wait until a dereference to go wrong. Writing int* p = nullptr provides a clear null state, but you must still validate the target and the scope afterward.
Dangling comes from the target ending its lifetime first, for example returning the address of a local object, keeping a reference to an already erased element, or borrowing a temporary string. The original pointer's bits usually do not change automatically, and you do not gain access rights just because “the address has not been reused yet.” The fact that the object has already ended matters more than memory contents still looking present.
Container modifications can also invalidate borrows
A vector reallocation invalidates pointers, references, and iterators to old elements. reserve does not change size, yet it may trigger reallocation, so you cannot assume only insertion and deletion are dangerous. The container still being alive is not enough; you must also check the invalidation rules of the specific operation for element borrows.
The example saves only an element index before expansion and does not keep a pointer used across the modification; after expansion it obtains the access location again. An index is not a universal identity either: if insertions, deletions, or sorting happen earlier, it may point at a different logical element. When you need a stable business identity, use a key or an explicit handle scheme rather than treating an index as a permanent address.
Design borrows instead of detecting bad addresses
The simplest strategy is to keep the borrow shorter than the owner and place potentially invalidating modifications outside the borrow. In a synchronous function, finish reading through a local reference before modifying the container; if an asynchronous task must keep accessing the data, store an owning copy, or under a shared-ownership model use weak_ptr::lock to check and obtain lifetime protection.
Setting one pointer to null only changes that pointer; it cannot clear copies, references, and views elsewhere. The example's optional pointer starts from nullptr and is used only while the container is stable. For hard-to-reproduce problems, ASan can detect many use-after-free cases, and MemorySanitizer targets uninitialized values; tools help locate issues but cannot replace interface contracts.
Pitfalls
- Nulling the owner after delete does not null every observer at the same time; old aliases remain unusable.
- String views, spans, and iterators can also dangle; you cannot find lifetime bugs by searching only for variables with asterisks.
Run an example
Minimum C++11 · complete program · Download .cpp
#include <cassert>
#include <cstddef>
#include <vector>
int main() {
std::vector<int> values{10, 20, 30};
const std::size_t index = 1;
int* selected = nullptr;
assert(selected == nullptr);
values.reserve(100);
values.push_back(40);
selected = &values.at(index);
assert(*selected == 20);
*selected = 25;
assert(values.at(index) == 25);
}
Compile locally
g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread memory-dangling.cpp -o example && ./exampleExpected result
Expected: exit 0, no output; every assert holds.
CHECK YOUR UNDERSTANDING
Close the answer. Explain it.
You have saved int* p = &values[0], and next you will call a push_back that may reallocate. How do you avoid a dangling read of p afterward?
Show a reference answer
If you only need the element value, copy that value before the modification. If you need to access the original logical position again after the modification, and you only append at the end, save index zero, then after push_back obtain a new reference with values.at(0) and do not read the old p. If rearrangement or deletion is also possible, look up again with a stable business key rather than extending the old pointer or blindly trusting the index.
Check the sources
Drafts and official chapters change. The version mark is only the example’s minimum.