Dynamic Memory in C++
Dynamic memory is the mechanism for allocating memory at runtime (while the program is executing), not at compile time. In C++, we can allocate memory on the heap using the new operator and free it using the delete operator.
Key differences between static and dynamic memory:
- Static memory — Allocated at compile time, fixed size, managed by the compiler. Example: a static array
int arr[10];. - Dynamic memory — Allocated at runtime, the size can change, managed by the programmer (or by smart pointers). Example:
new int(10).
The operators used:
- new — Allocates memory for one object and returns a pointer to that object.
- delete — Frees the memory allocated by new.
- new[] — Allocates memory for an array of objects.
- delete[] — Frees the memory allocated by new[].
Misuse of dynamic memory (forgetting delete, double delete, use-after-free) can cause memory leaks, dangling pointers, and undefined behavior. Therefore, smart pointers (unique_ptr, shared_ptr) are strongly recommended as a modern alternative for dynamic memory management.
Code Example
#include <iostream>
#include <memory>
#include <cstring>
using namespace std;
// ==================== Basic new and delete ====================
void basicDynamicMemory() {
cout << "=== Basic new and delete ===" << endl;
// Allocate a single integer
int* ptr = new int(42);
cout << "*ptr = " << *ptr << endl;
delete ptr;
ptr = nullptr; // Avoid a dangling pointer
// Allocate an array
int* arr = new int[5];
for (int i = 0; i < 5; i++) {
arr[i] = i * 10;
}
cout << "Array: ";
for (int i = 0; i < 5; i++) {
cout << arr[i] << " ";
}
cout << endl;
delete[] arr; // Delete the array with delete[]
arr = nullptr;
}
// ==================== Dynamic Array with User-Input Size ====================
void dynamicArrayFromInput() {
cout << "\n=== Dynamic Array from Input ===" << endl;
int n;
cout << "Enter the number of elements: ";
cin >> n;
// Allocate an array with a size from user input
int* scores = new int[n];
for (int i = 0; i < n; i++) {
scores[i] = (i + 1) * 10;
}
cout << "Scores: ";
for (int i = 0; i < n; i++) {
cout << scores[i] << " ";
}
cout << endl;
delete[] scores;
scores = nullptr;
}
// ==================== Dynamic 2D Array ====================
void dynamic2DArray() {
cout << "\n=== Dynamic 2D Array ===" << endl;
int rows = 3, cols = 4;
// Allocate a 2D array
int** matrix = new int*[rows];
for (int i = 0; i < rows; i++) {
matrix[i] = new int[cols];
}
// Fill the matrix
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = i * cols + j + 1;
}
}
// Print the matrix
cout << "Matrix " << rows << "x" << cols << ":" << endl;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
cout << matrix[i][j] << "\t";
}
cout << endl;
}
// Deallocate (in reverse order!)
for (int i = 0; i < rows; i++) {
delete[] matrix[i]; // Delete each row
}
delete[] matrix; // Delete the pointer array
matrix = nullptr;
}
// ==================== Memory Leaks ====================
void memoryLeakExample() {
cout << "\n=== Memory Leak ===" << endl;
// DANGEROUS! Memory leak - no delete
// int* bad = new int[1000];
// The pointer is overwritten without delete
// int* bad2 = new int[1000]; // bad can no longer be accessed, memory leaks
// The correct way:
int* good = new int[1000];
delete[] good;
good = nullptr;
cout << "Memory leak avoided with proper delete." << endl;
}
// ==================== Placement New ====================
void placementNewExample() {
cout << "\n=== Placement New ===" << endl;
// Allocate a buffer
char buffer[sizeof(int)];
// Placement new: construct an object in an existing buffer
int* placed = new (buffer) int(42);
cout << "Placed value: " << *placed << endl;
// Placement new for an array in a buffer
char arrayBuffer[sizeof(int) * 3];
int* placedArr = new (arrayBuffer) int[3]{10, 20, 30};
cout << "Placed array: ";
for (int i = 0; i < 3; i++) {
cout << placedArr[i] << " ";
}
cout << endl;
// Important: placement new does not manage the buffer memory
// No delete needed, but you must call the destructor manually if necessary
placed->~int(); // Call the destructor manually
}
// ==================== Smart Pointers as the Solution ====================
void smartPointerSolution() {
cout << "\n=== Smart Pointer as the Solution ===" << endl;
// unique_ptr for arrays
unique_ptr<int[]> smartArr(new int[5]);
for (int i = 0; i < 5; i++) {
smartArr[i] = i * 100;
}
cout << "Smart array: ";
for (int i = 0; i < 5; i++) {
cout << smartArr[i] << " ";
}
cout << endl;
// No delete needed! It is freed automatically when the unique_ptr goes out of scope
// make_unique for arrays (C++14)
auto smartArr2 = make_unique<int[]>(5);
for (int i = 0; i < 5; i++) {
smartArr2[i] = (i + 1) * 50;
}
cout << "make_unique array: ";
for (int i = 0; i < 5; i++) {
cout << smartArr2[i] << " ";
}
cout << endl;
// shared_ptr for an object
auto sharedObj = make_shared<int>(999);
cout << "Shared value: " << *sharedObj << endl;
}
// ==================== Dynamic Memory with a Class ====================
class DynamicArray {
private:
int* data;
int size;
public:
DynamicArray(int s) : size(s) {
data = new int[size];
for (int i = 0; i < size; i++) {
data[i] = 0;
}
cout << "DynamicArray: allocated " << size << " elements." << endl;
}
~DynamicArray() {
delete[] data;
data = nullptr;
cout << "DynamicArray: deallocated memory." << endl;
}
void set(int index, int value) {
if (index >= 0 && index < size) {
data[index] = value;
}
}
int get(int index) const {
if (index >= 0 && index < size) {
return data[index];
}
return -1;
}
void print() const {
cout << "[";
for (int i = 0; i < size; i++) {
cout << data[i];
if (i < size - 1) cout << ", ";
}
cout << "]" << endl;
}
};
int main() {
basicDynamicMemory();
dynamicArrayFromInput();
dynamic2DArray();
memoryLeakExample();
placementNewExample();
smartPointerSolution();
cout << "\n=== Dynamic Memory with a Class ===" << endl;
DynamicArray arr(5);
arr.set(0, 10);
arr.set(1, 20);
arr.set(2, 30);
arr.print();
// Dynamic object
DynamicArray* dynObj = new DynamicArray(3);
dynObj->set(0, 100);
dynObj->print();
delete dynObj; // Must be deleted manually!
cout << "\n=== Best Practices ===" << endl;
cout << "1. Use smart pointers (unique_ptr/shared_ptr) instead of raw new/delete." << endl;
cout << "2. If you must use new, make sure there is a corresponding delete." << endl;
cout << "3. Use new[] with delete[]; never mix them." << endl;
cout << "4. Set the pointer to nullptr after delete to avoid a dangling pointer." << endl;
cout << "5. Use RAII (Resource Acquisition Is Initialization) for resource management." << endl;
return 0;
}
Code Explanation
-
int* ptr = new int(42)— Allocates memory on the heap for a single integer with an initial value of 42. Returns a pointer to that integer. -
delete ptr— Frees the memory allocated bynew. Only for a single object, not for an array. -
int* arr = new int[5]— Allocates memory for a 5-element array. The elements are not initialized (random values). -
delete[] arr— Frees the array memory. You must usedelete[]for anew[]allocation, not plaindelete. -
int** matrix = new int*[rows]— Dynamically allocates a 2D array. The rows are allocated as an array of pointers, then each row is allocated separately. -
Placement new
new (buffer) int(42)— Constructs an object in an existing buffer. Useful for memory pools or embedded systems. It does not manage the buffer memory. -
unique_ptr<int[]>— A smart pointer for arrays that frees memory automatically. Replaces manualnew[]/delete[]. -
Destructor
~DynamicArray()— In a class that uses dynamic memory, the destructor must free the allocated memory to prevent a memory leak.
Everyday Use Cases
Imagine you are building a sensor data processing application. Sensor data arrives in real time and the amount is not fixed. Dynamic memory allows you to allocate memory as needed:
// Reading sensor data whose count is not known in advance
int sensorCount;
cin >> sensorCount;
double* sensorData = new double[sensorCount];
for (int i = 0; i < sensorCount; i++) {
cin >> sensorData[i];
}
// Process the data
double avg = 0;
for (int i = 0; i < sensorCount; i++) {
avg += sensorData[i];
}
avg /= sensorCount;
delete[] sensorData;
// Or better, use a vector:
vector<double> sensorData2(sensorCount);
// ... without needing a manual delete
Conclusion
Dynamic memory enables allocating memory at runtime using new/delete. Use new[] with delete[] for arrays. Avoid memory leaks by always freeing memory after using it. Smart pointers (unique_ptr, shared_ptr) are the safer, recommended modern solution. Placement new is useful for constructing objects in an existing buffer. For most cases, use std::vector instead of a manual dynamic array.