CppDynamic Memory (new / delete)

Dynamic Memory (new / delete)

So far, every variable you have created has lived on the stack — memory that is automatically allocated when a variable comes into scope and automatically freed when it goes out of scope. Sometimes you need memory that outlives the function that created it, or whose size isn't known until the program is running. That memory comes from the heap, and in C++ you manage it yourself with new and delete.
Stack vs heap

Aspect

Stack

Heap

Allocation

Automatic, on scope entry

Manual, via new

Deallocation

Automatic, on scope exit

Manual, via delete

Speed

Very fast

Slower (bookkeeping overhead)

Lifetime

Tied to the enclosing scope

Lives until explicitly freed

Size limits

Small, fixed at compile time (per frame)

Large, limited by available memory

Risk of leaks

None

Yes, if delete is forgotten

Allocating with new
The new operator allocates memory on the heap for a single object and returns a pointer to it. That memory stays valid until you explicitly free it — it does not disappear when the function that allocated it returns.

new_basics.cpp

CPP
#include <iostream>

int main() {
    int* p = new int;       // allocates one uninitialized int on the heap
    *p = 42;
    std::cout << *p << std::endl;

    int* q = new int(100);  // allocates and initializes to 100
    std::cout << *q << std::endl;

    delete p;                 // free p's memory
    delete q;                 // free q's memory

    return 0;
}
Every new must have exactly one matching delete
Memory allocated with new is not freed automatically. If you forget to call delete, that memory is never returned to the system for the lifetime of the program — a memory leak (covered in depth next chapter). If you call delete more than once on the same pointer, or delete memory that was never allocated with new, the result is undefined behavior, often a crash. Every successful new needs exactly one delete — no more, no fewer.
new[] and delete[] for arrays
Allocating an array on the heap uses new[], and it must be freed with the matching delete[] — not plain delete.

new_array.cpp

CPP
int size = 5;
int* arr = new int[size];   // heap-allocated array of 5 ints

for (int i = 0; i < size; i++) {
    arr[i] = i * i;
}

for (int i = 0; i < size; i++) {
    std::cout << arr[i] << " ";
}
std::cout << std::endl;

delete[] arr;   // must use delete[], not delete, for array allocations
Never mix delete with new[]
Calling plain delete on memory allocated with new[] (or delete[] on memory allocated with plain new) is undefined behavior. The array form typically stores extra bookkeeping (like the element count) that only delete[] knows how to interpret correctly. Always match new with delete, and new[] with delete[] — never cross them.
Why raw new/delete is now considered bad practice
Manually pairing every new with exactly one delete — across every code path, including early returns and exceptions — is genuinely difficult to get right in any non-trivial program. Forget one delete and you leak memory; call one twice and you corrupt the heap; throw an exception between new and delete and the cleanup never runs at all. This is precisely the class of bug that motivated modern C++'s move toward smart pointers, which wrap a raw pointer in an object that automatically calls delete at the right time — no matter how the enclosing scope is exited. The next few chapters build up to exactly that.
  • new / delete manage single objects; new[] / delete[] manage arrays — never mix the two forms.

  • Every heap allocation needs exactly one matching deallocation, on every possible code path.

  • Modern C++ code should reach for smart pointers (covered soon) instead of calling new/delete directly.

Coming up
The next chapter walks through concrete memory leak and dangling pointer examples — the exact failure modes that raw new/delete makes easy to fall into.