C++ PROGRAMMING ยท 15 MIN STUDY

Object lifetime and dangling references

Understand when C++ objects begin and end their lifetime, how storage duration differs from scope, and why pointers or references can become invalid.

Learning objectives

  • Distinguish scope, storage duration, and object lifetime.
  • Explain automatic and dynamic storage duration.
  • Identify dangling pointers and references.
  • Return values safely without referring to expired local objects.

Prerequisites: understand pointers, references, functions, and block scope. Review Pointers, References, Functions, and Scope and lifetime first.

Three ideas that are related but different

  • Scope: where a name can be used in source code.
  • Storage duration: how long the storage for an object is reserved.
  • Lifetime: the period during which the object is alive and its value may be used according to the language rules.

A name can leave scope while an object continues to exist, and an object's lifetime can end even while a pointer variable that once referred to it still exists.

Automatic storage duration

Ordinary local variables generally have automatic storage duration. Their lifetime ends when execution leaves the block in which they were created.

int calculate() {
    int result{42}; // automatic object
    return result;  // returns a copy of the value
}

Returning the value is safe: the caller receives the returned integer. Returning a reference to that local object is not safe.

int& broken() {
    int local{42};
    return local; // WRONG: local lifetime ends when function returns
}

The returned reference would refer to an object whose lifetime has ended. Using it is undefined behavior. A compiler may warn about this pattern.

Dynamic storage duration

An object created with a dynamic allocation operation can outlive the block that initiated the allocation, but its lifetime must still be managed. Modern C++ normally uses an owning smart pointer rather than a raw owning pointer.

#include <memory>

auto number = std::make_unique<int>(42);
// The int remains alive while number owns it.
// When number is destroyed, its int is destroyed too.

The smart pointer itself is a local object. The dynamically allocated integer's lifetime is tied to ownership held by that smart pointer.

Dangling pointers and references

A dangling pointer or dangling reference no longer refers to a live object. Common causes include returning the address of a local variable, keeping a pointer after an object is destroyed, or retaining a reference to an element after a container operation invalidates it.

int* pointer = nullptr;
{
    int temporary{7};
    pointer = &temporary;
} // temporary lifetime ends
// pointer is now dangling; do not dereference it

Setting a pointer to nullptr after destroying an object can prevent accidental reuse through that particular pointer, but it does not repair other aliases that may still point to the object.

Safe design habits

  • Prefer returning values when practical; compilers can optimize many returns.
  • Do not return pointers or references to local automatic objects.
  • Keep non-owning pointers and references shorter-lived than the objects they refer to.
  • Use standard containers and smart pointers to express ownership.
  • After operations that can invalidate iterators, pointers, or references, do not use old handles unless the container's rules guarantee validity.

Practice exercises

  1. Explain why returning an int by value from calculate() is safe but returning int& to a local variable is not.
  2. At what point does the local number object below end its lifetime: after its declaration, after the print, or at the closing brace?
  3. Give two ways a pointer can become dangling.
  4. Why does setting one dangling pointer to nullptr not guarantee that all aliases are safe?

Solutions

Show exercise solutions
  1. The returned integer is a value copied or otherwise returned to the caller; the local object ends with the function. A reference would still designate that expired local object.
  2. At the closing brace of the block containing it.
  3. For example, returning the address of a local variable, or using a pointer after the pointed-to object has been destroyed.
  4. Other pointer or reference variables may still hold the old address. Each alias must be managed so it is not used after the object's lifetime ends.

Summary

Scope controls name visibility; storage duration describes storage; lifetime describes when an object exists. Never use a pointer or reference after its target's lifetime ends. Prefer value returns, standard containers, and RAII ownership.

References