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.
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?
Reveal 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.
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.
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.
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
- 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.
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.
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 likeShape* p = read_shape(); followed by using p and nothing else
Why it failsThe function transfers responsibility to the caller when it returns the address, so the caller must perform the delete.
Do this insteadTreat 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 likeA vector of Shape pointers goes out of scope after its pointers are used
Why it failsThe container stores addresses, so destroying it removes the addresses but not the objects they refer to.
Do this insteadDelete 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 failsOnce the last pointer to an object is overwritten or discarded, the object is unreachable, and no later delete can reach it.
Do this insteadDelete 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.
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
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
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
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.