Concepts / Pointers and Dynamic Memory in C++

Pointers and Dynamic Memory in C++

Heap memory is the usual leak source in C++ because it is the resource whose release depends entirely on the programmer remembering to perform it.

  • Programming

A Leak That Leaves No Trace

Some of the most damaging bugs in C++ produce no error message, no crash, and no wrong answer on the screen. The program appears to work correctly for every test you run, yet its memory use keeps creeping upward each time it executes a particular path. Something was obtained and never given back. That pattern is a resource leak, and it is worth understanding before you write any code that asks for memory at runtime.

What do you think happens?

A program passes every test you run, but its memory use grows a little each time one particular path executes. What is the most likely explanation?

  • The compiler has a bug and should be replaced

  • Some resource was acquired on that path and never released

  • The program is correct, and memory use always grows slightly

  • A local variable is being cleaned up twice

Reveal answer
Answer:

Some resource was acquired on that path and never released

This is the definition of a resource leak. A resource, such as dynamically allocated memory, is acquired but never released, so it stays reserved yet unreachable. Because the program still produces correct answers, the leak is easy to miss.

Keep one question in mind as you read: at each point in the program, who is responsible for releasing the memory, and has that responsibility actually been carried out? Almost every leak in C++ comes down to a broken answer to that question.

Why Memory Is the Usual Resource

A resource leak can involve any resource that is acquired and must be released, but memory is the one this course focuses on, because C++ hands it to you explicitly. When you ask for an object to be created dynamically, the object lives in dynamically allocated memory, sometimes called the free store, until you release it yourself. Nothing reclaims it automatically at the end of a block the way a local variable is cleaned up.

This explicit lifetime is the source of both the power and the danger of dynamic memory. The power is that an object can outlive the function that created it, and can be passed around as needed. The danger is that the program, not the language, is responsible for ending that lifetime. Automatic variables are released for you when their block ends. Dynamically allocated objects are released only when code explicitly deletes them, so any path that fails to do so leaves them behind.

Key point

Heap memory is the usual leak source in C++ because it is the resource whose release depends entirely on the programmer remembering to perform it.

Who Holds the Object After a Return

Functions often build objects and hand them back to the caller as pointers. The function creates the object in dynamic memory, returns its address, and then its own work is finished. Whoever received that address now holds the only way to reach the object. The caller has inherited the duty to release it. Ownership, in this sense, means being the party that must eventually call delete on the object.

The sequence of events matters more than any single line. The object is created by the function, the address travels back through the return, and the caller stores that address in a pointer variable. Each step is ordinary. The leak occurs only if the chain of responsibility is broken at the last step, where nobody calls delete.

Tracing the read_shape Handoff

Consider a function named read_shape() that reads a description from input, constructs a Shape object on the heap, and returns a pointer to it. The function itself does nothing wrong. The danger lies in the caller. Trace the handoff step by step using the sequence below.

callerowns the pointer variableread_shape()builds the objectfree storeholds the Shape objectcall read_shape()create Shape on heapreturn pointerscope ends, no delete
After read_shape returns, the caller is the only holder of the address. If the caller never calls delete, the ownership duty is never discharged.

Read the diagram from top to bottom. The caller calls read_shape(), which creates the object and returns its address. The caller then uses the pointer, reads what it needs, and lets the pointer variable go out of scope. The pointer variable disappears, but the object it referred to does not. The object is now unreachable while still occupying memory, which is precisely a leak.

cpp
Notes on the code
  • read_shape() allocates on the free store and returns the address; it is not at fault
  • p is the only variable holding the address of the new Shape
  • When report() ends, p is destroyed but the Shape it pointed to is not
cpp
Notes on the code
  • The caller, who received ownership, deletes the object exactly once
  • The fix belongs in the caller, not in read_shape()

Containers That Hold Addresses

A second opportunity for a leak appears when objects are stored in a container through pointers. A container of Shape pointers holds addresses, not the shapes themselves. Removing the pointers from the container, or destroying the container, does not by itself release the objects they refer to. Those objects remain allocated until somebody explicitly deletes them.

shapes (vector)destroyed at end of scopeslot 0stores an addressShape Astill allocatedslot 1stores an addressShape Bstill allocatedholdsholdspoints topoints to
The vector's slots hold addresses. Destroying the vector removes the slots, but the Shape objects they pointed to remain allocated on the free store.

The diagram shows why the container cannot clean up its elements on its own. The vector owns the slots that hold the addresses, and those slots are removed when the vector goes out of scope. The Shape objects sit in heap memory, separate from the vector. Once the addresses are gone, nothing in the program can reach the objects, yet they stay reserved.

The source describes the owner of such a container as the party who might forget to delete the pointed-to objects. Ownership is the key idea. Someone must be designated as the owner, and that owner must release every element. If the container is handed around, the question of who owns its elements has to be answered as clearly as the question of who owns the container.

The Life of an Allocated Block

It helps to think of a dynamically allocated object as moving through a small set of states. It begins as nothing, is acquired from the free store, and is then either released properly or left behind. The leak is the state that a program reaches when the last reachable pointer disappears before a release has happened.

Not allocatedno object yetReleaseddelete has been calledAllocatedreserved on the free storeUnreachableleaked, no pointer remainsnewdeletelast pointer lost
An allocated object can end in a released state, or it can reach the unreachable state when its last pointer is lost without a delete.

Trace the two outgoing transitions from the Allocated state. The transition labeled delete leads to the Released state, which is the correct ending. The other transition, in which the last pointer is lost, leads to the Unreachable state. Nothing in the program can ever reach the object again, so no later line can release it. That is the transition that produces a leak instead of a release.

Mistakes That Create Leaks

  • Common mistake

    Assuming that a function which returns a pointer also releases the object

    What it looks like

    Shape* p = read_shape(); followed by using p and nothing else

    Why it fails

    The function transfers responsibility to the caller when it returns the address, so the caller must perform the delete.

    Do this instead

    Treat every returned free-store pointer as a duty to release, and delete it in the code that holds it.

  • Common mistake

    Believing that destroying a container of pointers destroys the objects

    What it looks like

    A vector of Shape pointers goes out of scope after its pointers are used

    Why it fails

    The container stores addresses, so destroying it removes the addresses but not the objects they refer to.

    Do this instead

    Delete each pointed-to object explicitly before the container is destroyed, and document which code owns the elements.

  • Common mistake

    Losing the only copy of a pointer by overwriting it

    Why it fails

    Once the last pointer to an object is overwritten or discarded, the object is unreachable, and no later delete can reach it.

    Do this instead

    Delete the object before reassigning the pointer, or keep track of every place the address is stored.

  • Common mistake

    Checking only the normal path through a function, and ignoring the branches and early returns where a delete is skipped

Clear Ownership as the Defense

The main defense against resource leaks is clear ownership, documented at function interfaces and enforced at every exit path. When you write a function that returns a free-store pointer, state in its interface that the caller owns the result. When you store pointers in a container, decide which code deletes the pointed-to objects, and make sure that code runs whether the function returns normally or leaves early.

Best practice

For each allocation, name the owner in your head or in a comment, then check every path out of the function to confirm that the owner has released the object or passed ownership on.

Finding the Leak in a Short Snippet

cpp
Notes on the code
  • make_shape() returns ownership of a new free-store object to its caller
  • Two objects are acquired inside process()
  • Each branch of the if statement must be checked separately
MEDIUM

Read the snippet above and trace every path through process(). Name each object that is never deleted, and state which path causes the leak. Then rewrite process() so that both objects are released on every path.

Hints
  • Count the acquisitions, then count the deletes on each branch of the if statement
  • The early return skips the delete that appears at the bottom of the function
  • Each object needs one delete on every path, and the delete must come before its last pointer is lost
Debugging

When keep is true, the program deletes a and returns, so b is never deleted and its object becomes unreachable. The fix is to delete b before that early return as well, for example by deleting both objects in each branch, or by restructuring so that a single exit path releases everything.

Key Takeaways

  • A resource leak occurs when a resource, such as dynamically allocated memory, is acquired but never released, leaving it reserved yet unreachable.
  • A function that returns a pointer to a free-store object transfers responsibility for releasing that object to the caller, and losing the pointer without deleting the object causes a leak.
  • A container of pointers stores addresses rather than the objects themselves, so destroying the container does not release the objects, and the owner must delete each one explicitly.
  • Clear ownership, documented at function interfaces and enforced on every exit path, is the main defense against resource leaks.

More concepts starting with P