TRT
Sign in

Foundations · Module 3 · Lesson 10 of 10

Dangling Pointers and Use-After-Free

See how a pointer can outlive its object, how a freed block gets reused so that a stale pointer corrupts unrelated data, and which ownership designs and tools prevent or catch these bugs.

FreeAbout 8 minutes

By the end you can

  • Identify the common ways in which a pointer or reference becomes dangling
  • Trace what happens when a freed block is reused while a stale pointer still exists
  • Explain why setting one pointer to nullptr after delete does not make a program safe
  • Choose ownership designs and tools that prevent or detect use-after-free

Builds on The Heap: new, delete and Memory Leaks.

01 · A question first

The pointer still holds a perfectly valid-looking address, the memory is still there, and the program seems to work. What does dangling mean then? Why can a bug like this lie dormant for months, and why do attackers love it?

Make a guess, then step through the trace below to check it.

02 · Watch it run

A stale pointer writes into someone else's objectstep 1 of 6
int* p = new int(5);int* q = p;delete p;int* r = new int(9);*q = 1;
  1. item 0p: 5000(looked at in this step)
  2. item 1q: --
  3. item 2r: --
  4. item 3block @5000: 5(looked at in this step)

p holds the address 5000 of a heap block containing 5. The four cells are the pointers p, q and r, and the block itself. The pointers q and r do not exist yet.

Machine work: allocate a block, store 5, set p to 5000

03 · The idea

Start with the problem

Lesson 9 covered forgetting to free memory. The opposite mistake is freeing it, or losing it, while someone still holds its address. A pointer, or a reference, to an object that no longer exists is called dangling. Here is code that looks harmless:

cpp
int* makeNumber() {
    int x = 42;
    return &x;          // the address of a local variable
}

The caller receives a pointer into a stack frame that was popped when the function returned, as lesson 6 of module 2 described. Compilers warn about this simple form with -Wreturn-local-addr, but the same bug has subtler forms, and the stale pointer still looks like a perfectly good address.

How the idea works

Lifetime. Every object has a lifetime. It begins when the object is constructed and ends when it is destroyed: at the end of its scope, at a delete, when a container reallocates, or at the end of the statement for a temporary value. A pointer is only valid while its object is alive. But a pointer is nothing more than an address, as lesson 6 explained, so nothing in it records that the object has died.

Four common ways to dangle.

  1. After a delete. Using the memory afterwards is called a use-after-free (trace, step 3).
  2. A pointer or reference to a local variable that has gone out of scope, as in the example above.
  3. A pointer, reference or iterator into a vector after it reallocated, as in lesson 4.
  4. A view or reference to a temporary that has been destroyed, such as std::string_view v = std::string("hi");, or the pointer from c_str() after the string changed, as in lesson 5.

What happens when you use it. The behavior is undefined, and the outcomes vary. If the memory has not been reused, the program often seems to work. If it has been reused, the stale pointer reads or writes the new owner's data, silently, as steps 4 and 5 show. If the memory was returned to the operating system, the program crashes. Which one you get depends on the allocation pattern, so the bug can hide for months.

Double free. Calling delete a second time on a stale pointer frees a block that the allocator may already have given to someone else, or corrupts the allocator's own lists. Attackers can exploit that.

Defenses.

  • Give every object one clear owner, and keep raw pointers and references as observers that never outlive it.
  • Set a pointer to nullptr after delete. A later use then fails cleanly, and delete nullptr is harmless. It does not help for other copies of the pointer, such as q in the trace.
  • Prefer values, containers and std::unique_ptr, which free automatically and make the owner explicit. The Core level introduces smart pointers, including std::shared_ptr and std::weak_ptr for shared ownership, where weak_ptr can tell whether the object is still alive.
  • Use tools. AddressSanitizer, with -fsanitize=address, poisons freed blocks and keeps them out of circulation for a while, so it reports a use-after-free the moment it happens (step 6). Valgrind does the same, and newer GCC versions add warnings such as -Wdangling-pointer and -Wuse-after-free.

Why it is correct

The language leaves this to you because tracking lifetimes would cost something. Checking every pointer against the life of its target would need either a garbage collector or a compile-time proof system, and C++ chose neither as the default, in return for zero overhead. What C++ does guarantee is when scopes end and when destructors run. If you design with clear ownership and RAII, your pointers can never outlive their owners, and tools can verify the design.

The reason that reuse is so common is the allocator's policy. To keep memory hot in the cache, it tends to hand out the most recently freed block of a suitable size first, as step 4 shows. That makes a stale pointer land on a live object quickly, and it makes the damage invisible at the point where it is done.

What it costs

Debugging. The symptom is far from the cause: an unrelated object holds a wrong value, or the program fails only under certain loads. These bugs can take days to find without a sanitizer.

Security. Use-after-free is one of the leading classes of exploitable memory bugs. An attacker arranges for the freed block to be reused by data under their control, so that the stale pointer reads or writes it, and from there can take over the flow of the program. Browser vendors have reported it among their top causes of serious vulnerabilities.

Defenses. AddressSanitizer typically makes a program about twice as slow and uses two to three times the memory, so it belongs in testing. A std::unique_ptr costs nothing extra: it is the size of a raw pointer. A std::shared_ptr costs a 16-byte control block and an atomic counter update on every copy, typically a few to a few tens of nanoseconds.

Pitfalls and variations

Nulling only one copy. Setting p to null after delete leaves q dangling, as in step 3.

Returning addresses of locals and temporaries. Return a value, or a pointer to something that outlives the call.

References across container changes. Holding a reference or iterator while the container grows is the dangling bug of lesson 4.

Callbacks and stored pointers. An event handler or a stored this pointer can outlive the object it refers to. Lambdas that capture locals by reference have the same trap when they outlive the function, a topic of the Core level.

Threads. A thread that keeps using a local variable after its function has returned is a dangling reference in disguise.

Variations. Hardened allocators and hardware memory tagging on some ARM processors can detect use-after-free even in production. The Rust language prevents these bugs at compile time with its borrow checker, and the C++ Core Guidelines describe lifetime rules that tools such as clang-tidy can partly enforce.

Where it is used

This knowledge is the core of memory-safe C++ practice. Teams run sanitizers and fuzzers in their continuous integration, adopt the rule that raw pointers never own, and use smart pointers and containers for ownership. Browser projects have built protective pointer types that turn a use-after-free into a safe crash instead of an exploit.

That completes the third module. You can now store and index collections, treat strings as bytes and as objects, follow pointers, manage the heap, and recognize the bugs that live there. The next module introduces structs, enumerations and files, and then the Core level opens with classes and RAII, which turn the ownership rules of the last two lessons into guarantees the language enforces for you.

04 · Prove it

Question 1 of 4 · answer from memory

Which function returns a dangling pointer?

05 · Practice

Something wrong with this lesson? Report a problem

Not started

Sign in to save your learning progress.

Sign in to save