SrcForge

Lesson 65

Dangling Pointers

Covers what dangling pointers are, common causes, pointer vs object lifetime, smart pointer solutions, and best practices for avoiding undefined behavior.

Lesson content

What are Dangling Pointers in C++?

Dangling Pointers in C++ are pointers that refer to memory that is no longer valid. This usually happens when the memory has been released, the object has gone out of scope, or the pointer references an object that no longer exists.

Imagine borrowing the key to a hotel room. After checking out, the room is assigned to someone else, but you still have the old key. Although you possess the key, it no longer grants access to your original room. A dangling pointer behaves similarly, it still stores a memory address, but that address no longer belongs to the original object.

Dangling pointers are one of the most common sources of undefined behavior in C++. They can cause crashes, incorrect results, security vulnerabilities, and difficult-to-find bugs.

Why do dangling pointers occur?

C++ gives programmers direct control over memory management. This flexibility comes with responsibility. If object lifetimes are not managed carefully, pointers may continue pointing to memory that is no longer valid.

Real-world use cases

  • Dynamic memory allocation
  • Linked lists and trees
  • Game engines
  • Operating systems
  • Embedded systems
  • Database engines
  • High-performance applications

Prerequisites

  • Variables and data types
  • Functions
  • Pointers and references
  • Dynamic memory (new and delete)
  • Object lifetime
  • Basic debugging with GDB (recommended)

What is a dangling pointer?

A dangling pointer is a pointer that still stores a memory address after the object it points to has been destroyed or is no longer valid.

int* ptr = new int(10);

delete ptr;

// ptr is now dangling

The pointer still contains the old address, but that memory is no longer owned by the program.

Common causes of dangling pointers

  • Deleting allocated memory — pointer still holds the freed address
  • Returning the address of a local variable — local variable is destroyed when the function returns
  • Pointer to a local variable — variable goes out of scope
  • Multiple pointers to the same object — one pointer deletes the object while others still reference it
  • Container reallocation — some operations invalidate pointers to container elements

Why are dangling pointers dangerous?

Dereferencing a dangling pointer results in undefined behavior. Possible outcomes include program crash, segmentation fault, data corruption, random values, security vulnerabilities, and bugs that appear only occasionally.

Pointer lifetime vs object lifetime

A pointer and the object it points to have independent lifetimes. First an object is created, then a pointer stores its address. When the object is destroyed, the pointer still contains the old address, becoming dangling. The pointer variable still exists, but the object no longer does.

Example 1: Dangling pointer after delete

#include <iostream>
using namespace std;

int main()
{
    int* ptr = new int(50);

    cout << *ptr << endl;

    delete ptr;

    // ptr is now dangling

    cout << *ptr << endl; // Undefined behavior

    return 0;
}

// Correct version:
#include <iostream>
using namespace std;

int main()
{
    int* ptr = new int(50);

    cout << *ptr << endl;

    delete ptr;

    ptr = nullptr; // Prevent dangling pointer

    return 0;
}

Example 2: Returning a local variable's address

#include <iostream>
using namespace std;

int* getValue()
{
    int value = 100;

    return &value; // Wrong
}

int main()
{
    int* ptr = getValue();

    cout << *ptr << endl; // Undefined behavior

    return 0;
}

// Correct version:
#include <iostream>
using namespace std;

int getValue()
{
    int value = 100;

    return value; // Safe return
}

int main()
{
    int value = getValue();

    cout << value << endl;

    return 0;
}

Example 3: Pointer to a local variable

#include <iostream>
using namespace std;

int main()
{
    int* ptr;

    {
        int number = 25;

        ptr = &number;
    } // number is destroyed here

    cout << *ptr << endl; // Undefined behavior

    return 0;
}

Example 4: Multiple pointers

#include <iostream>
using namespace std;

int main()
{
    int* ptr1 = new int(10);

    int* ptr2 = ptr1;

    delete ptr1;

    cout << *ptr2 << endl; // ptr2 is dangling

    return 0;
}

// Correct version:
delete ptr1;

ptr1 = nullptr;

ptr2 = nullptr;

Example 5: Using smart pointers

#include <iostream>
#include <memory>
using namespace std;

int main()
{
    unique_ptr<int> ptr = make_unique<int>(75);

    cout << *ptr << endl;

    return 0;
}

// Output: 75

std::unique_ptr automatically destroys the object when it goes out of scope, preventing dangling pointers caused by manual delete.

Common mistakes and pitfalls

  • Dereferencing a pointer after delete. Fix: set the pointer to nullptr after deleting
  • Returning the address of a local variable. Fix: return by value or use dynamic allocation with clear ownership
  • Using pointers after scope ends. Fix: ensure the object outlives the pointer
  • Sharing raw pointers carelessly. Fix: define ownership or use smart pointers
  • Assuming a pointer becomes nullptr after delete. Fix: assign nullptr manually if needed
// Wrong
int* ptr = new int(5);

delete ptr;

cout << *ptr;

// Correct
int* ptr = new int(5);

delete ptr;

ptr = nullptr;

if (ptr != nullptr)
{
    cout << *ptr;
}

Best practices

  • Prefer std::unique_ptr for exclusive ownership
  • Use std::shared_ptr only when shared ownership is required
  • Set raw pointers to nullptr after calling delete
  • Avoid returning pointers to local variables
  • Clearly document ownership when passing raw pointers between functions
  • Use references instead of pointers when ownership and nullability are not required
  • Use standard containers such as std::vector instead of manually managed dynamic arrays
  • Enable compiler warnings (-Wall -Wextra -Wpedantic) and use sanitizers such as AddressSanitizer during development

When not to use this

Avoid raw pointers when you need automatic resource management, when a smart pointer can express ownership more clearly, when a reference is sufficient, when a standard container can manage memory automatically, or when multiple objects need coordinated ownership without manual bookkeeping. Modern C++ encourages using RAII (Resource Acquisition Is Initialization) and smart pointers to minimize memory management errors.

Summary

  • A dangling pointer points to memory that is no longer valid
  • Common causes include deleting memory, returning local addresses, and accessing objects after their lifetime ends
  • Dereferencing a dangling pointer results in undefined behavior
  • Setting a pointer to nullptr after delete helps prevent accidental use of freed memory
  • Smart pointers significantly reduce the risk of dangling pointers
  • Understanding object lifetime is essential for writing safe C++ code
  • Memory analysis tools such as AddressSanitizer and Valgrind can help detect lifetime-related bugs

Frequently asked questions

What is a dangling pointer in C++?

A dangling pointer is a pointer that still refers to a memory location after the object it pointed to has been destroyed or deallocated.

Does delete automatically set a pointer to nullptr?

No. Calling delete releases the allocated memory, but the pointer itself still contains the old address. You must assign nullptr manually if you plan to reuse the pointer variable.

Can smart pointers prevent dangling pointers?

In many cases, yes. Smart pointers such as std::unique_ptr automatically manage object lifetimes and reduce the risk of using invalid pointers. However, you can still create dangling raw pointers by storing addresses obtained from managed objects without careful lifetime management.

What is the difference between a dangling pointer and a memory leak?

A dangling pointer refers to memory that has already been released, while a memory leak occurs when allocated memory is never released.

How can I detect dangling pointers?

Tools such as AddressSanitizer (ASan), Valgrind, compiler warnings, and debuggers like GDB can help identify many lifetime and invalid memory access issues during development.