Dynamic Memory Allocation in C++
When you declare a regular variable like int x = 5;, memory is reserved for it at compile time and released automatically when the variable goes out of scope. This is called stack memory. But what if you do not know how much memory you need until the program is actually running? That is where dynamic memory allocation comes in.
Dynamic memory allocation lets your program request memory during runtime from a region called the heap. You decide exactly how much memory to grab and when to release it. This gives your program great flexibility — but also places the responsibility of cleanup on you.
Stack vs Heap — The Key Difference
┌──────────────────────────────────────────────────────────┐
│ MEMORY │
├──────────────────┬───────────────────────────────────────┤
│ STACK │ HEAP │
│ │ │
│ • Fixed size │ • Large, flexible size │
│ • Auto-managed │ • Manually managed (new / delete) │
│ • Fast access │ • Slightly slower access │
│ • Local vars │ • Dynamic arrays, objects │
│ • Auto-released │ • Released only when you say so │
└──────────────────┴───────────────────────────────────────┘
Allocating Memory with new
The new operator requests memory from the heap and returns a pointer to that memory.
Syntax:
data_type* pointer = new data_type;
Example — Single Variable:
#include <iostream>
using namespace std;
int main() {
int* p = new int; // request memory for one int on the heap
*p = 42; // store a value in that memory
cout << "Value: " << *p << endl;
cout << "Address: " << p << endl;
delete p; // release the memory
p = nullptr; // good practice: avoid dangling pointer
return 0;
}
Output:
Value: 42
Address: 0x1a2b3c4d (varies each run)
Initialize at Allocation:
int* p = new int(100); // allocate and initialize to 100
Releasing Memory with delete
Every block of memory you allocate with new must be released with delete when you are done using it. Failing to do this causes a memory leak — the memory stays occupied for the entire lifetime of the program even though you no longer need it.
int* p = new int(55);
// ... use p ...
delete p; // free the memory
p = nullptr; // reset pointer to safe state
Dynamic Arrays
A major use of dynamic allocation is creating arrays whose size is determined at runtime. Regular arrays require a fixed, compile-time size. Dynamic arrays do not.
Syntax:
data_type* array = new data_type[size];
Example:
#include <iostream>
using namespace std;
int main() {
int n;
cout << "How many numbers? ";
cin >> n;
int* arr = new int[n]; // allocate n integers on the heap
for (int i = 0; i < n; i++) {
arr[i] = (i + 1) * 10;
}
cout << "Values: ";
for (int i = 0; i < n; i++) {
cout << arr[i] << " ";
}
cout << endl;
delete[] arr; // use delete[] for arrays (NOT delete)
arr = nullptr;
return 0;
}
Sample Output (n = 4):
How many numbers? 4
Values: 10 20 30 40
Always use delete[] (with brackets) for dynamically allocated arrays. Using plain delete on an array causes undefined behavior.
Dynamic 2D Arrays
Creating a 2D array dynamically requires allocating an array of pointers, then allocating each row separately.
#include <iostream>
using namespace std;
int main() {
int rows = 3, cols = 4;
// Step 1: allocate array of row pointers
int** matrix = new int*[rows];
// Step 2: allocate each row
for (int i = 0; i < rows; i++) {
matrix[i] = new int[cols];
}
// Fill and print
int val = 1;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = val++;
cout << matrix[i][j] << "\t";
}
cout << endl;
}
// Step 3: free each row first, then the pointer array
for (int i = 0; i < rows; i++) {
delete[] matrix[i];
}
delete[] matrix;
matrix = nullptr;
return 0;
}
Output:
1 2 3 4
5 6 7 8
9 10 11 12
Memory Allocation for Objects
new also works for objects. It calls the class constructor automatically. delete calls the destructor.
#include <iostream>
using namespace std;
class Car {
public:
string brand;
Car(string b) : brand(b) {
cout << brand << " created." << endl;
}
~Car() {
cout << brand << " destroyed." << endl;
}
};
int main() {
Car* c = new Car("Toyota"); // constructor called
cout << "Using: " << c->brand << endl;
delete c; // destructor called
return 0;
}
Output:
Toyota created.
Using: Toyota
Toyota destroyed.
Common Dynamic Memory Problems
┌─────────────────────┬────────────────────────────────────────────────┐
│ Problem │ Description │
├─────────────────────┼────────────────────────────────────────────────┤
│ Memory Leak │ new without delete — memory wasted forever │
│ Dangling Pointer │ Using a pointer after delete — crash/corruption│
│ Double Delete │ Calling delete twice — undefined behavior │
│ Delete vs Delete[] │ Using delete instead of delete[] on an array │
│ Null Dereference │ Using a nullptr pointer — program crash │
└─────────────────────┴────────────────────────────────────────────────┘
Checking if Allocation Succeeded:
int* p = new(nothrow) int[1000000000]; // huge allocation
if (p == nullptr) {
cout << "Memory allocation failed!" << endl;
} else {
// use p
delete[] p;
}
new vs malloc — C++ vs C Style
| Feature | new (C++) | malloc (C) |
|---|---|---|
| Language | C++ | C and C++ |
| Type safety | Yes (returns typed pointer) | No (returns void*) |
| Calls constructor | Yes | No |
| On failure | Throws exception (or nullptr with nothrow) | Returns NULL |
| Pair with | delete / delete[] | free() |
Always prefer new and delete in C++ code. Use smart pointers (covered in a later topic) for even safer memory management.
Key Takeaways
- Dynamic allocation lets you request memory at runtime using
new. - Memory from
newmust be released withdeleteto avoid memory leaks. - Use
new[]anddelete[]for arrays — never mix them with the non-array versions. - Set pointers to
nullptrafter deleting to avoid dangling pointer bugs. - Dynamic allocation is essential when the size of data is not known at compile time.
