new and delete
Every variable you declare in the normal way — int x = 5;, std::string name; — lives on the stack: automatic storage that the compiler manages for you. But sometimes you do not know at compile time how much memory you need, or you need an object to outlive the function that created it. For those situations, C++ gives you the free store (also called the heap): a large pool of memory managed at runtime. The new operator allocates from the free store, and the delete operator returns it. Understanding these operators is important — but so is understanding why, in modern C++, you rarely need to call them directly.
The free store
When a program starts, the operating system gives it two main regions of memory. The stack holds local variables and function call frames; it is fast but fixed in size. The free store is a much larger region that you can tap into on demand. You ask for a block of memory, get a pointer to it, use it for as long as you need, and then tell the runtime you are done with it so the memory can be reused. Because the free store is managed at runtime rather than compile time, the size of an allocation can depend on a value that is not known until the program actually runs — for example, the number of records in a file the user just opened.
The important tradeoff is responsibility: stack memory is reclaimed automatically when the enclosing scope ends, but free-store memory stays allocated until you explicitly free it. Forgetting to free it is a bug called a memory leak; freeing it twice or still using it after freeing it are even more serious bugs. This burden of manual ownership is why modern C++ strongly prefers containers like std::vector and smart pointers like unique_ptr — they automate all of this for you.
Allocating and freeing a single object
The new operator allocates one object of the given type on the free store, calls its constructor if it has one, and returns a pointer to it. The type of that pointer is T* where T is the type you requested. When you are done, you pass that same pointer to delete, which calls the destructor and returns the memory to the free store.
int* p = new int; // allocate one int on the free store
*p = 42; // write through the pointer
std::cout << *p << "\n"; // read through the pointer
delete p; // MUST return the memory when done
// p now points to freed memory — do not use it again
p = nullptr; // optional safety: make the dangling pointer harmless
// You can also initialize at allocation time:
int* q = new int{99}; // allocates and sets to 99
delete q;The pointer p is just a local variable on the stack; what it points to lives on the free store. When p goes out of scope, the pointer variable disappears — but the memory it pointed to does not. That is the source of memory leaks: letting the pointer go away without first calling delete.
Allocating arrays: new[] and delete[]
To allocate an array on the free store, write new T[n]. Unlike a stack-allocated array, n does not need to be a compile-time constant — it can be any runtime integer expression. The result is a pointer to the first element, and you can use it with the [] subscript operator exactly like an ordinary array. To free an array allocated with new[], you must use delete[] — not plain delete. Using the wrong form is undefined behaviour and typically corrupts the heap.
int n;
std::cout << "How many scores? ";
std::cin >> n;
double* scores = new double[n]; // n determined at runtime — fine!
for (int i = 0; i < n; ++i)
scores[i] = 0.0;
// ... use scores ...
delete[] scores; // MUST use delete[], not delete, for arrays
scores = nullptr;This is exactly the mechanism std::vector uses internally: when it runs out of room, it allocates a bigger array with new[], copies elements over, and frees the old one with delete[]. The vector class wraps all of that in RAII so you never have to think about it.
// Manual grow: what std::vector does for you automatically:
double* bigger = new double[2 * n];
for (int i = 0; i < n; ++i)
bigger[i] = scores[i];
delete[] scores;
scores = bigger;
n = 2 * n;Common errors — and why they are dangerous
Misusing new and delete does not produce compile errors — the program compiles cleanly and then misbehaves at runtime, often in ways that are hard to reproduce or diagnose. The table below shows the most common mistakes:
| Code | Error |
|---|---|
int* p; *p = 5; delete p; | Using an uninitialized pointer. p was never assigned by new, so it points to a random location. |
int* p = new int; *p = 5; p = new int; | Memory leak. The first allocated block is never deleted — its address is lost when p is overwritten. |
int* p = new int[10]; *p = 5; delete p; | Wrong delete form. delete[] must be used for arrays; plain delete is undefined behaviour. |
int* p = new int[10]; int* q = p; delete[] p; delete[] q; | Double delete. Both p and q point to the same block; deleting it twice corrupts the heap. |
int n = 4; int* p = &n; *p = 5; delete p; | Deleting a non-heap pointer. You can only delete memory you obtained from new. |
Dangling pointers
A dangling pointer is a pointer that points to memory that has already been freed. Using it is undefined behaviour: the program may crash immediately, produce wrong results silently, or appear to work correctly until a later run or a different compiler or platform. The freed memory may have been handed to a completely different part of the program and now contains unrelated data, so writing through a dangling pointer can corrupt the state of other objects.
int* values = new int[n];
// ... process values ...
delete[] values; // memory is freed
// Some other work here — the runtime may reuse that memory for something else
values[0] = 42; // UNDEFINED BEHAVIOUR — dangling pointer write
// The program may crash, silently corrupt data,
// or seem to work (until it doesn't)A common defensive practice is to set the pointer to nullptr immediately after deleting it. That way, any accidental dereference crashes visibly instead of silently corrupting data. It is not a complete solution — another copy of the same pointer may still dangle — but it catches many bugs early.
Memory leaks
A memory leak occurs when you allocate memory with new and then lose the only pointer to it without calling delete. The memory block can never be freed for the remainder of the program's lifetime, because you no longer have the address to pass to delete. Small, one-time leaks in short-lived programs are generally harmless — the operating system reclaims all memory when the program exits. But leaks inside loops, or in programs that run continuously (servers, daemons, GUI applications), will eventually exhaust available memory and cause a crash.
void process() {
int* data = new int[1000];
// ... do work ...
// function returns — data goes out of scope
// but the free-store allocation does NOT — it is now leaked!
// If process() is called thousands of times, the leak accumulates.
}
// Fix: delete before returning
void process_fixed() {
int* data = new int[1000];
// ... do work ...
delete[] data; // always, even on every return path
}Why you rarely need new and delete directly
The C++ standard library was designed so that most of the situations that would have required raw new and delete in older C++ are now handled by safer abstractions. If you need a dynamically-sized sequence of elements, use std::vector — it allocates and reallocates internally and frees everything in its destructor. If you need to allocate a single object whose lifetime is not tied to a scope, use std::unique_ptr or std::shared_ptr — they are wrappers that call delete automatically when the last owner goes away (RAII for heap objects). These wrappers cost nothing at runtime compared to raw pointers.
// Old style — manual ownership, easy to leak or double-delete:
int* arr = new int[n];
// ... use arr ...
delete[] arr; // must not forget; must not do twice
// Modern style — std::vector owns the memory, cleans up automatically:
std::vector<int> arr(n);
// ... use arr ...
// no delete needed — destructor handles it
// Old style for a single object:
Widget* w = new Widget{args};
// ... use w ...
delete w;
// Modern style — unique_ptr owns and deletes automatically:
auto w = std::make_unique<Widget>(args);
// ... use *w or w->member ...
// deleted when w goes out of scopeThe guideline in modern C++ is: if you find yourself writing new outside of a constructor or delete outside of a destructor, pause and ask whether a standard container or smart pointer would serve instead. In the vast majority of cases it will, and you get all the same performance with none of the ownership hazards.
Key rules to remember
Every new must be matched by exactly one delete
Too few deletes → memory leak. Too many deletes (double-delete) → heap corruption and potential crash. The count must be exactly one.
Use delete[] for arrays, delete for single objects — never mix them
new int[n] must be freed with delete[], not delete. Using the wrong form is undefined behaviour, even if it sometimes appears to work.
Never use a pointer after deleting it
A pointer to freed memory is a dangling pointer. Any read or write through it is undefined behaviour. Set the pointer to nullptr immediately after delete as a safety net.
Only delete memory you obtained from new
Calling delete on a stack variable's address, a null pointer (safe but a no-op), or an already-deleted pointer are all errors.
In modern C++, prefer std::vector and smart pointers over raw new/delete
std::vector manages dynamic arrays automatically. std::unique_ptr and std::shared_ptr manage single objects. Both have the same runtime cost as raw pointers but none of the ownership hazards.