TRT
Sign in

Foundations · Module 3 · Lesson 9 of 10

The Heap: new, delete and Memory Leaks

Learn where objects can live beyond a function call, how new and delete work with the allocator, and how leaks and mismatched deletes arise, so that you see why modern C++ lets containers and smart pointers do the work.

FreeAbout 9 minutes

By the end you can

  • Explain when to use the heap instead of the stack
  • Match new with delete and new[] with delete[]
  • Recognize a memory leak, including one caused by overwriting the only pointer
  • Explain why modern C++ prefers containers and smart pointers to raw new and delete

Builds on Pointers: Variables That Hold Addresses, The Call Stack: What Happens When a Function Is Called.

01 · A question first

A local variable disappears when its function returns. What if you need an object that must survive, or an array whose size is known only at run time? Where does that memory come from, who decides when it is returned, and what happens when nobody does?

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

02 · Watch it run

Allocating, overwriting a pointer, and leakingstep 1 of 5
int* p = new int(5);*p = 6;p = new int(7);delete p;
  1. item 0p: 5000(looked at in this step)
  2. item 1A @5000: 5(looked at in this step)
  3. item 2B: --

new int(5) asks the allocator for a block of heap memory and puts 5 in it. The allocator finds a free block, here at address 5000, and new returns that address, which is stored in the stack variable p. The cells are p and two heap blocks, A and B. Block B does not exist yet.

Machine work: allocator: find a free block, store 5 in it, return its address

03 · The idea

Start with the problem

A local variable lives in its function's frame and is gone when the function returns, as lesson 6 of module 2 showed. That is perfect for most variables, but three situations need something else. An object may have to outlive the function that creates it. An array may have a size that is known only when the program runs. And some data is too large for a stack of a few megabytes.

All three need storage whose lifetime you control yourself. C++ provides it in a region of memory called the heap, also called the free store.

How the idea works

Two kinds of storage. Variables on the stack are created and destroyed automatically with their scope. Objects on the heap are created by an explicit request and live until an explicit release. The stack is fast and limited, and the heap is larger, slower and your responsibility.

new and delete. The expression new allocates memory and returns its address. The expression delete gives it back:

cpp
int* p = new int(5);        // allocate one int on the heap, holding 5
*p = 6;
delete p;                   // give it back
 
int* arr = new int[100];    // allocate 100 ints, not initialized
arr[0] = 1;
delete[] arr;               // arrays need delete[]

The pointer is the only handle to the object, as step 1 shows. A new must be matched by a delete, and a new[] by a delete[]. If memory runs out, new throws a std::bad_alloc exception.

Behind the code: the allocator. The expression new calls a function named operator new, which in practice calls the C library's malloc. The allocator behind it obtains large chunks of memory from the operating system, splits them into blocks, and keeps track of which blocks are free. Each block carries a small header of bookkeeping and is aligned to 16 bytes, so a lone new int occupies about 32 bytes with a typical glibc allocator. The expression delete hands the block back to the allocator's free list for reuse. The operating system does not necessarily get it back at once.

Leaks. A memory leak is a block that is still allocated but that no pointer reaches any more. Step 3 shows how it happens: the only copy of the address was overwritten. Nobody can free the block, and it stays until the program ends. A single leak is harmless. A leak that happens on every request or every loop iteration is not.

The modern alternative. Most C++ code never writes new or delete. A std::vector, as lesson 3 showed, owns its heap buffer and frees it in its destructor. A smart pointer such as std::make_unique<int>(5) owns a single object and frees it when it goes out of scope. This technique is called RAII, and the Core level covers it. You learn raw new and delete here so that you know what the owners do for you.

Why it is correct

The allocator's contract is simple: the memory is yours from new until the matching delete, and any other use is undefined behavior. The language cannot free heap memory automatically, because it does not know when the last use of an object ends. It only knows scopes. So ownership becomes a design decision: exactly one piece of code must be responsible for freeing each object.

RAII encodes that decision in the type system. An owner object frees its memory in its destructor, and the language guarantees when destructors run: at the end of the scope, even when an exception leaves the scope early. That is a guarantee that manual delete calls cannot match.

What it costs

Allocation time. A heap allocation typically takes tens to a few hundred nanoseconds. Allocating on the stack is a single change to a register and costs around a nanosecond. A loop that allocates a million small objects spends tens of milliseconds on the allocator.

Space overhead. The header and the alignment mean that a 4-byte object can occupy 32 bytes, an eightfold waste, and the allocator needs bookkeeping memory as well.

Fragmentation. After many allocations and frees, free memory can be scattered in small holes. A large request may fail even when the total free memory would be enough.

Cache behavior. Objects allocated at different times may be far apart, so walking through them is slower than walking through one contiguous array.

Leaks. A service that leaks 100 bytes per request at 1,000 requests per second loses 100 KB every second, roughly 8.6 GB a day, until it is killed or the machine slows to a crawl.

Pitfalls and variations

A missing delete. An early return or an exception between new and delete skips the release. This is the most common source of leaks.

Overwriting the only pointer. Step 3 of the trace is the classic example.

Double delete. Freeing the same block twice is undefined behavior, and often crashes with a message such as free(): double free detected.

Mismatched forms. delete on an array, delete[] on a single object, and free on something from new are all undefined behavior.

Deleting what was not allocated. Passing the address of a local variable to delete is undefined behavior.

Using freed memory. The next lesson covers this. Note that delete p does not change p, so it keeps the old address.

Uninitialized memory. new int[100] holds garbage, while new int[100]() sets every element to zero.

Variations. malloc and free are the C functions and must not be mixed with new and delete. new (std::nothrow) returns null instead of throwing. Custom allocators and std::pmr arrive in the Advanced level. To find leaks, build with -fsanitize=address, which includes LeakSanitizer on Linux, or run the program under Valgrind.

Where it is used

Every standard container, std::string and every smart pointer uses the heap internally. Objects that are created in one place and used in another, trees and graphs, polymorphic objects chosen at run time, and large buffers all live there. Understanding the heap is what lets you diagnose memory growth in a running service and what makes RAII make sense.

The next lesson, "Dangling Pointers and Use-After-Free", looks at the other half of heap trouble: a pointer that outlives the object it points to.

04 · Prove it

Question 1 of 4 · answer from memory

Which situation most clearly needs heap allocation rather than a local variable?

05 · Practice

Something wrong with this lesson? Report a problem

Not started

Sign in to save your learning progress.

Sign in to save