Dangling pointers in C++ are pointers that refer to memory that no longer belongs to the program: a local variable that has gone out of scope, a container that has reallocated or been destroyed, or a block that has been freed. Dereferencing one is , and it is one of the hardest bugs to diagnose because the program usually crashes somewhere other than where the mistake was made.undefined behaviour
The four ways a pointer dangles
Returning the address of a local. The local is destroyed when the function returns, so the pointer points at a dead stack frame.
int* bad_pointer() {
int value = 42; // destroyed when the function returns
return &value; // the caller receives a dangling pointer
}Pointing past the end of an array. The memory is still there in this case, but reading it is still .undefined behaviour
int values[3] = {1, 2, 3};
int* beyond = values + 3; // one past the end: legal to form, not to dereferenceKeeping a pointer into a container that reallocates. When a std::vector grows, it may move its elements to a new block, and every pointer into the old block dangles.
#include
#include
int main() {
std::vector durations;
durations.push_back(180);
int* into_the_vector = &durations[0];
std::cout << "first: " << *into_the_vector << '\n';
// Growing the vector may move the elements to a new block.
for (int i = 0; i < 100; ++i) {
durations.push_back(i * 10);
}
std::cout << "size now: " << durations.size() << '\n';
std::cout << "address moved: " << (into_the_vector != &durations[0]) << '\n';
return 0;
}The last line compares the old address with the new one and reports that the vector moved its elements. The old pointer now refers to memory the vector no longer owns, and dereferencing it would be : it might read 180 by luck, or garbage, or crash. The rule is simple: never keep a pointer or a reference into a container across an operation that might reallocate it.undefined behaviour
Using memory after freeing it. A pointer to a deleted block is dangling until the memory is reused.
int* block = new int[10];
delete[] block;
block = nullptr; // reset it so nobody uses itWhy the crash is never where the bug is
A dangling pointer rarely causes a fault at the moment it becomes dangling. The memory is still there, still readable, and often still holds the value it had. The crash comes later, when that memory is reused for something else.
int* bad_address_of_local() {
Track track = {"Fading", 200};
return &track.seconds; // track is destroyed on return
}
int main() {
int* dangling = bad_address_of_local();
std::cout << "value read: " << *dangling << '\n'; // undefined behaviour
}The program may print 200, or a different number, or crash. All three are legal, because the behaviour is undefined. This is exactly why the rule is not "be careful" but "do not write it in the first place". The two safe rewrites are below.
#include
#include
struct Track {
std::string title;
int seconds;
};
int safe_by_value() {
Track track = {"Fading", 200};
return track.seconds; // a copy travels home, nothing dangles
}
int main() {
std::cout << "value read: " << safe_by_value() << '\n';
return 0;
}The output is now defined and identical on every run:
value read: 200The fix: prefer values, then references, then smart pointers
The safest hierarchy for ownership is worth memorising.
- Use a value when the object is small and you want an independent copy. Nothing can dangle.
- Use a reference or a const reference as a function parameter when you are borrowing an object that the caller owns.
- Use a smart pointer when you need to share or transfer ownership.
The smart pointer is the modern answer to the new and delete pattern, and it removes the entire category of dangling-pointer bugs caused by forgetting to free.
#include
#include
#include
struct Track {
std::string title;
int seconds;
};
int main() {
auto owned = std::make_unique(Track{"Owned", 200});
std::cout << owned->title << " " << owned->seconds << '\n';
auto shared = std::make_shared(Track{"Shared", 240});
std::cout << shared->title << " " << shared->seconds << '\n';
return 0;
}The output shows both smart pointers working like ordinary pointers, with no delete anywhere:
Owned 200
Shared 240The unique_ptr frees its object when it goes out of scope. The shared_ptr frees it when the last owner goes out of scope. Neither can dangle, because neither lets you read the object after it is gone.
Rules that keep you safe
- Never return the address of a local variable. Return the value, or return a reference to an object that outlives the function.
- Never let a pointer or reference into a container survive a possible reallocation. Use indexes, or re-derive the pointer after the change.
- Set a pointer to
nullptrimmediately after freeing the memory it pointed at. - Prefer stack objects and values. Reach for
newonly when the size genuinely has to be decided at runtime. - Use the address sanitizer while developing:
-fsanitize=addressfinds use-after-free and out-of-bounds accesses at the exact line.
Quick reference
- A pointer dangles when the memory it refers to is destroyed, freed or moved.
- Returning the address of a local is the most common cause.
- Growing a
std::vectorcan invalidate every pointer and reference into it. - The crash usually appears far from the mistake.
- Values and references cannot dangle; smart pointers cannot dangle.
- Compile with
-fsanitize=addressand run your tests — it catches these bugs immediately.
Try it yourself: deliberately write the "return the address of a local" function and call it, and observe how the behaviour changes when you add a local variable to [[main]] between the call and the use. Then replace it with a version that returns the value instead, and a version that returns a reference to a parameter. Finally, build the same program with [[-fsanitize=address]] and watch the tool point at the exact line.