README
2 min read18 headings
Dynamic Memory in C++
Table of Contents
Stack vs Heap
Stack Memory
- Automatic allocation by compiler
- Fast - just moves stack pointer
- Limited size (usually 1-8 MB)
- Automatic cleanup when scope ends
- LIFO (Last In, First Out)
void stackExample() {
int x = 10; // Stack allocated
double arr[100]; // Stack allocated array
string name = "Hi"; // Stack (string object; data may be heap)
} // All automatically destroyed here
Heap Memory
- Manual allocation with new/delete
- Slower - involves memory manager
- Large - limited by available RAM
- Manual cleanup required
- Flexible - allocate any size at runtime
void heapExample() {
int* ptr = new int(10); // Heap allocated
double* arr = new double[100]; // Heap allocated array
// Must manually deallocate
delete ptr;
delete[] arr;
}
Memory Layout
High Address
┌─────────────────┐
│ Stack │ ↓ Grows down
│ (local vars) │
├─────────────────┤
│ ↓ │
│ │
│ ↑ │
├─────────────────┤
│ Heap │ ↑ Grows up
│ (dynamic alloc) │
├─────────────────┤
│ BSS/Data │ (global/static)
├─────────────────┤
│ Code │ (program)
└─────────────────┘
Low Address
new and delete
Allocating Single Objects
// Allocate integer
int* intPtr = new int; // Uninitialized
int* intPtr2 = new int(42); // Initialized to 42
int* intPtr3 = new int{42}; // C++11 uniform init
// Allocate object
string* strPtr = new string("Hello");
MyClass* objPtr = new MyClass(arg1, arg2);
// Don't forget to delete!
delete intPtr;
delete intPtr2;
delete intPtr3;
delete strPtr;
delete objPtr;
What new Does
- Allocates memory from heap
- Calls constructor (for objects)
- Returns pointer to allocated memory
What delete Does
- Calls destructor (for objects)
- Returns memory to heap
Handling Allocation Failure
// By default, new throws std::bad_alloc on failure
try {
int* huge = new int[1000000000000];
} catch (const bad_alloc& e) {
cout << "Allocation failed: " << e.what() << endl;
}
// nothrow version returns nullptr on failure
int* ptr = new(nothrow) int[1000000000000];
if (ptr == nullptr) {
cout << "Allocation failed" << endl;
}
Dynamic Arrays
Allocation and Deallocation
// Allocate array
int* arr = new int[10]; // 10 uninitialized ints
int* arr2 = new int[10](); // 10 zero-initialized ints
int* arr3 = new int[5]{1,2,3,4,5}; // Initialized with values
// Access elements normally
arr[0] = 100;
for (int i = 0; i < 10; i++) {
arr[i] = i * 10;
}
// MUST use delete[] for arrays!
delete[] arr;
delete[] arr2;
delete[] arr3;
delete vs delete[]
int* single = new int(42);
int* array = new int[10];
delete single; // OK: single object
delete[] array; // OK: array
// delete array; // WRONG! Undefined behavior
// delete[] single; // WRONG! Undefined behavior
Dynamic Size at Runtime
int size;
cout << "Enter size: ";
cin >> size;
int* dynamicArr = new int[size]; // Size determined at runtime
for (int i = 0; i < size; i++) {
dynamicArr[i] = i * 2;
}
delete[] dynamicArr;
2D Dynamic Arrays
// Allocate 2D array (rows x cols)
int rows = 3, cols = 4;
// Method 1: Array of pointers
int** matrix = new int*[rows];
for (int i = 0; i < rows; i++) {
matrix[i] = new int[cols];
}
// Use it
matrix[1][2] = 42;
// Deallocate (reverse order!)
for (int i = 0; i < rows; i++) {
delete[] matrix[i];
}
delete[] matrix;
// Method 2: Single contiguous block (better cache performance)
int* flat = new int[rows * cols];
// Access: flat[row * cols + col]
flat[1 * cols + 2] = 42;
delete[] flat;
Memory Management Issues
1. Memory Leak
Allocated memory that is never freed.
void memoryLeak() {
int* ptr = new int(42);
// Oops! Forgot to delete
} // ptr goes out of scope, memory is lost forever
// Fix
void noLeak() {
int* ptr = new int(42);
// ... use ptr ...
delete ptr; // Don't forget!
}
2. Dangling Pointer
Pointer to memory that has been freed.
int* ptr = new int(42);
delete ptr;
// ptr is now dangling!
cout << *ptr; // UNDEFINED BEHAVIOR
// Fix: Set to nullptr after delete
delete ptr;
ptr = nullptr;
3. Double Free
Freeing the same memory twice.
int* ptr = new int(42);
delete ptr;
delete ptr; // UNDEFINED BEHAVIOR - crash likely!
// Fix: Set to nullptr after delete
delete ptr;
ptr = nullptr;
delete ptr; // OK: deleting nullptr is safe
4. Use After Free
Using memory after it's been freed.
int* ptr = new int(42);
int* alias = ptr;
delete ptr;
cout << *alias; // UNDEFINED BEHAVIOR - alias is dangling too!
5. Array/Non-Array Mismatch
int* arr = new int[10];
delete arr; // WRONG! Should be delete[]
int* single = new int(42);
delete[] single; // WRONG! Should be delete
6. Uninitialized Pointer
int* ptr; // Uninitialized - contains garbage!
*ptr = 42; // UNDEFINED BEHAVIOR - crash likely!
// Fix: Initialize to nullptr
int* ptr = nullptr;
RAII Principle
Resource Acquisition Is Initialization
Core idea: Tie resource lifetime to object lifetime.
Example: Manual vs RAII
// Manual (error-prone)
void manual() {
int* data = new int[100];
if (someCondition) {
return; // LEAK! Forgot to delete
}
process(data); // Might throw exception - LEAK!
delete[] data;
}
// RAII (safe)
void raii() {
vector<int> data(100); // RAII container
if (someCondition) {
return; // OK! vector destructor frees memory
}
process(data); // If throws, destructor still called
} // vector destructor automatically called
Custom RAII Class
class IntArray {
private:
int* data;
int size;
public:
IntArray(int n) : size(n) {
data = new int[n]; // Acquire resource
}
~IntArray() {
delete[] data; // Release resource
}
int& operator[](int i) { return data[i]; }
// Prevent copying (Rule of Three issue)
IntArray(const IntArray&) = delete;
IntArray& operator=(const IntArray&) = delete;
};
void useRAII() {
IntArray arr(100);
arr[0] = 42;
} // Destructor automatically frees memory
Best Practices
✅ Do
// 1. Use smart pointers instead of raw new/delete
#include <memory>
unique_ptr<int> ptr = make_unique<int>(42);
shared_ptr<int[]> arr = make_shared<int[]>(100);
// 2. Use containers instead of raw arrays
vector<int> arr(100); // Automatic memory management
// 3. Initialize pointers
int* ptr = nullptr;
// 4. Set to nullptr after delete
delete ptr;
ptr = nullptr;
// 5. Match new with delete, new[] with delete[]
int* single = new int(42);
delete single;
int* array = new int[10];
delete[] array;
// 6. Check for null before using
if (ptr != nullptr) {
*ptr = 10;
}
❌ Don't
// 1. Don't use raw new/delete when avoidable
int* ptr = new int(42); // Prefer smart pointers
// 2. Don't forget to delete
void leak() {
int* ptr = new int(42);
} // Memory leaked!
// 3. Don't use uninitialized pointers
int* ptr;
*ptr = 42; // Crash!
// 4. Don't double delete
delete ptr;
delete ptr; // Crash!
// 5. Don't mismatch new/new[] with delete/delete[]
int* arr = new int[10];
delete arr; // Wrong! Should be delete[]
// 6. Don't return pointer to local
int* bad() {
int x = 10;
return &x; // Dangling!
}
Modern C++ Guidelines
- Prefer stack allocation when possible
- Use
vectorinstead ofnew[] - Use smart pointers for dynamic allocation
- Follow Rule of Zero/Three/Five for classes with resources
- Use RAII for all resource management
Quick Reference
// Single object
int* ptr = new int(42);
delete ptr;
ptr = nullptr;
// Array
int* arr = new int[10];
delete[] arr;
arr = nullptr;
// Zero-initialized array
int* arr = new int[10]();
// Initialized array (C++11)
int* arr = new int[3]{1, 2, 3};
// nothrow version
int* ptr = new(nothrow) int[size];
if (!ptr) { /* handle failure */ }
// 2D array
int** matrix = new int*[rows];
for (int i = 0; i < rows; i++)
matrix[i] = new int[cols];
// Cleanup 2D
for (int i = 0; i < rows; i++)
delete[] matrix[i];
delete[] matrix;
// Better alternatives
#include <memory>
unique_ptr<int> ptr = make_unique<int>(42);
unique_ptr<int[]> arr = make_unique<int[]>(10);
vector<int> vec(10);
Compile & Run
g++ -std=c++17 -Wall -Wextra examples.cpp -o examples
./examples
For memory debugging:
# With Valgrind (Linux)
valgrind --leak-check=full ./examples
# With Address Sanitizer
g++ -std=c++17 -fsanitize=address -g examples.cpp -o examples
./examples