C++ Memory Management
Managing dynamic memory allocation and deallocation
🧠 What is C++ Memory Management?
C++ memory management involves controlling how programs allocate, use, and free memory during execution. It includes stack memory, heap memory, and techniques to prevent memory leaks.
// Basic memory allocation
int* ptr = new int(42); // Allocate
delete ptr; // Deallocate
Memory Management Concepts
Stack Memory
Automatic memory for local variables
int x = 10; // Stack allocation
char arr[100]; // Stack array
Heap Memory
Dynamic memory allocation
int* ptr = new int(20);
int* arr = new int[100];
Memory Deallocation
Free allocated memory
delete ptr; // Single object
delete[] arr; // Array
Smart Pointers
Smart pointers automate memory management, preventing leaks and simplifying ownership.
std::unique_ptr enforces exclusive ownership and is lightweight; std::shared_ptr allows
shared ownership via reference counting; std::weak_ptr breaks reference cycles. They call the
appropriate destructor automatically when the pointer goes out of scope. Using smart pointers eliminates most manual
delete calls, making code exception-safe and easier to reason about, which is foundational for robust
modern C++ applications.
unique_ptr<int> ptr =
make_unique<int>(42);
🔹 Stack vs Heap Memory
Stack memory is automatically managed, fast, and used for local variables, while heap memory is dynamically
allocated, larger, and manually controlled. Stack allocation is deterministic (LIFO) and limited in
size, suitable for short-lived data. Heap allocation via new/malloc is flexible for
objects with varying lifetimes or large data but requires explicit deallocation to prevent memory leaks, making
understanding the distinction crucial for performance and stability.
#include <iostream>
using namespace std;
void demonstrateStack() {
// Stack allocation - automatic cleanup
int stackVar = 100;
int stackArray[5] = {1, 2, 3, 4, 5};
cout << "Stack variable: " << stackVar << endl;
cout << "Stack array: ";
for (int i = 0; i < 5; i++) {
cout << stackArray[i] << " ";
}
cout << endl;
// Variables automatically destroyed when function ends
}
void demonstrateHeap() {
// Heap allocation - manual cleanup required
int* heapVar = new int(200);
int* heapArray = new int[5]{10, 20, 30, 40, 50};
cout << "Heap variable: " << *heapVar << endl;
cout << "Heap array: ";
for (int i = 0; i < 5; i++) {
cout << heapArray[i] << " ";
}
cout << endl;
// Must manually free memory
delete heapVar;
delete[] heapArray;
}
int main() {
cout << "=== Stack Memory Demo ===" << endl;
demonstrateStack();
cout << "\n=== Heap Memory Demo ===" << endl;
demonstrateHeap();
return 0;
}
Output:
=== Stack Memory Demo === Stack variable: 100 Stack array: 1 2 3 4 5 === Heap Memory Demo === Heap variable: 200 Heap array: 10 20 30 40 50
🔹 Dynamic Memory Allocation
Dynamic memory allocation lets you request memory at runtime using new and release it with
delete. Unlike stack variables, dynamically allocated memory persists until explicitly
freed, allowing flexible data structures like linked lists or arrays of unknown size. Always pair each
new with a delete to prevent memory leaks. For arrays, use new[] and
delete[]. Proper management ensures efficient resource use and prevents crashes or performance
degradation.
#include <iostream>
using namespace std;
int main() {
// Allocate single integer
int* singleInt = new int(42);
cout << "Single integer: " << *singleInt << endl;
// Allocate array of integers
int size = 5;
int* dynamicArray = new int[size];
// Initialize array
for (int i = 0; i < size; i++) {
dynamicArray[i] = (i + 1) * 10;
}
cout << "Dynamic array: ";
for (int i = 0; i < size; i++) {
cout << dynamicArray[i] << " ";
}
cout << endl;
// Check if allocation was successful
if (singleInt != nullptr && dynamicArray != nullptr) {
cout << "Memory allocation successful!" << endl;
}
// Free allocated memory
delete singleInt;
delete[] dynamicArray;
// Set pointers to nullptr to avoid dangling pointers
singleInt = nullptr;
dynamicArray = nullptr;
cout << "Memory freed and pointers set to nullptr" << endl;
return 0;
}
Output:
Single integer: 42 Dynamic array: 10 20 30 40 50 Memory allocation successful! Memory freed and pointers set to nullptr
🔹 Memory Leaks and Prevention
A memory leak occurs when dynamically allocated memory is not freed after use, gradually consuming available RAM and potentially crashing the program. Prevention strategies include always pairing allocations with deallocations, using RAII (Resource Acquisition Is Initialization) patterns, leveraging scope-bound resource management, and employing tools like valgrind for detection. In modern C++, preferring smart pointers over raw pointers automates this cleanup, dramatically reducing leak risks.
#include <iostream>
using namespace std;
// BAD: Memory leak example
void memoryLeakExample() {
int* ptr = new int(100);
cout << "Value: " << *ptr << endl;
// Missing delete - MEMORY LEAK!
// delete ptr; // This line is missing
}
// GOOD: Proper memory management
void properMemoryManagement() {
int* ptr = new int(200);
try {
cout << "Value: " << *ptr << endl;
// Some operations that might throw exceptions
delete ptr; // Always delete allocated memory
ptr = nullptr; // Set to nullptr after deletion
}
catch (...) {
delete ptr; // Clean up even if exception occurs
throw; // Re-throw the exception
}
}
// BETTER: Using RAII (Resource Acquisition Is Initialization)
class SafeIntWrapper {
private:
int* ptr;
public:
SafeIntWrapper(int value) : ptr(new int(value)) {}
~SafeIntWrapper() { // Destructor automatically called
delete ptr;
ptr = nullptr;
}
int getValue() const { return *ptr; }
void setValue(int value) { *ptr = value; }
};
int main() {
cout << "=== Memory Leak Example ===" << endl;
memoryLeakExample(); // This leaks memory!
cout << "\n=== Proper Memory Management ===" << endl;
properMemoryManagement();
cout << "\n=== RAII Example ===" << endl;
{
SafeIntWrapper wrapper(300);
cout << "RAII Value: " << wrapper.getValue() << endl;
// Destructor automatically called when wrapper goes out of scope
}
cout << "Memory automatically cleaned up!" << endl;
return 0;
}
Output:
=== Memory Leak Example === Value: 100 === Proper Memory Management === Value: 200 === RAII Example === RAII Value: 300 Memory automatically cleaned up!
🔹 Smart Pointers (C++11)
Smart pointers (unique_ptr, shared_ptr, weak_ptr) automatically manage
dynamic memory, eliminating the need for manual delete calls. unique_ptr
enforces exclusive ownership; shared_ptr allows shared ownership with reference counting;
weak_ptr breaks reference cycles. Adopting these from the C++ Standard Library prevents memory leaks
and dangling pointers, making code safer, more readable, and exception-safe by ensuring proper cleanup when objects
go out of scope.
#include <iostream>
#include <memory>
using namespace std;
int main() {
// unique_ptr - exclusive ownership
unique_ptr<int> uniquePtr = make_unique<int>(42);
cout << "unique_ptr value: " << *uniquePtr << endl;
// Transfer ownership
unique_ptr<int> anotherPtr = move(uniquePtr);
// uniquePtr is now nullptr
if (uniquePtr == nullptr) {
cout << "Original unique_ptr is now null" << endl;
}
cout << "Transferred value: " << *anotherPtr << endl;
// shared_ptr - shared ownership
shared_ptr<int> sharedPtr1 = make_shared<int>(100);
shared_ptr<int> sharedPtr2 = sharedPtr1; // Share ownership
cout << "shared_ptr value: " << *sharedPtr1 << endl;
cout << "Reference count: " << sharedPtr1.use_count() << endl;
// Array with smart pointers
unique_ptr<int[]> arrayPtr = make_unique<int[]>(5);
for (int i = 0; i < 5; i++) {
arrayPtr[i] = (i + 1) * 10;
}
cout << "Smart pointer array: ";
for (int i = 0; i < 5; i++) {
cout << arrayPtr[i] << " ";
}
cout << endl;
// No need to call delete - automatic cleanup!
cout << "Smart pointers will automatically clean up memory!" << endl;
return 0;
}
Output:
unique_ptr value: 42 Original unique_ptr is now null Transferred value: 42 shared_ptr value: 100 Reference count: 2 Smart pointer array: 10 20 30 40 50 Smart pointers will automatically clean up memory!