Memory Management Complete Guide
Stack vs Heap Advanced Topic

C++ Dynamic Memory: Complete Guide to Stack vs Heap Memory

Master C++ dynamic memory with comprehensive examples of stack vs heap memory, new/delete operators, memory management, smart pointers, and practical applications. Learn efficient memory allocation techniques.

Stack Memory

Automatic allocation

Heap Memory

Dynamic allocation

Smart Pointers

Memory safety

Memory Leaks

Prevention & detection

Understanding Memory in C++

C++ provides two main types of memory: stack and heap. Understanding the difference between them is crucial for writing efficient, safe, and correct C++ programs. Dynamic memory management allows programs to allocate memory at runtime, providing flexibility but also requiring careful management.

Memory Layout in C++ Program
Stack Memory
0x7fff5fbff8ac
localVar = 42
0x7fff5fbff8b0
temp = 3.14
0x7fff5fbff8b8
buffer[10]

Fast, automatic, LIFO structure

Heap Memory
0x100400000
new int[100]
0x100400190
new Object()
0x100400200
malloc(500)
0x100400400
Dynamic Data

Flexible, manual management, global access

Stack Memory
  • Allocation: Automatic
  • Speed: Very Fast
  • Size: Limited (MBs)
  • Lifetime: Function scope
  • Management: Compiler
Heap Memory
  • Allocation: Manual
  • Speed: Slower
  • Size: Large (GBs)
  • Lifetime: Programmer controlled
  • Management: Programmer
Smart Pointers
  • Allocation: Automatic
  • Speed: Fast
  • Safety: High
  • Lifetime: RAII controlled
  • Management: Automatic
Key Concepts:
  • Stack: Automatic, fast, limited size, function-scoped variables
  • Heap: Manual, flexible, large size, programmer-controlled lifetime
  • Dynamic Allocation: Allocating memory at runtime using new/delete
  • Memory Leak: Allocated memory that is never freed
  • RAII: Resource Acquisition Is Initialization - modern C++ pattern

1. Stack vs Heap Memory: Complete Comparison

Feature Stack Memory Heap Memory
Allocation Method Automatic by compiler Manual using new/malloc
Deallocation Automatic when scope ends Manual using delete/free
Access Speed Very fast (direct CPU instructions) Slower (requires pointer indirection)
Size Limit Small (typically 1-8 MB) Large (limited by OS/available RAM)
Memory Layout Contiguous, LIFO structure Fragmented, random access
Lifetime Function/scope lifetime Until explicitly freed
Flexibility Fixed size at compile time Dynamic size at runtime
Thread Safety Each thread has its own stack Shared across threads (requires synchronization)
Common Uses Local variables, function calls, return addresses Large data, dynamic arrays, objects with varying lifetime
Risk Factors Stack overflow, use-after-return Memory leaks, dangling pointers, fragmentation

Practical Examples: Stack vs Heap

Stack vs Heap Memory Examples
#include <iostream>
#include <string>
#include <chrono>
using namespace std;
using namespace std::chrono;

class Example {
private:
    int id;
    string name;
public:
    Example(int i, string n) : id(i), name(n) {
        cout << "Example " << id << " created: " << name << endl;
    }
    ~Example() {
        cout << "Example " << id << " destroyed: " << name << endl;
    }
    void display() {
        cout << "ID: " << id << ", Name: " << name << endl;
    }
};

void demonstrateStack() {
    cout << "\n=== STACK MEMORY DEMONSTRATION ===\n";
    
    // All variables below are on the stack
    int localInt = 42;                    // Integer on stack
    double localDouble = 3.14159;         // Double on stack
    char localChar = 'A';                 // Character on stack
    int localArray[5] = {1, 2, 3, 4, 5};  // Array on stack
    string localString = "Hello Stack";   // String object on stack (manages heap internally)
    
    // Object on stack
    Example stackObject(1, "Stack Object");
    stackObject.display();
    
    // Nested scope demonstration
    {
        int nestedVariable = 100;
        cout << "Nested variable: " << nestedVariable << endl;
        // nestedVariable will be automatically destroyed here
    }
    // nestedVariable no longer accessible here
    
    cout << "Stack variables will be automatically destroyed when function returns\n";
}

void demonstrateHeap() {
    cout << "\n=== HEAP MEMORY DEMONSTRATION ===\n";
    
    // Manual memory allocation on heap
    int* heapInt = new int(42);                    // Integer on heap
    double* heapDouble = new double(3.14159);      // Double on heap
    char* heapChar = new char('A');                // Character on heap
    int* heapArray = new int[5]{1, 2, 3, 4, 5};    // Array on heap
    Example* heapObject = new Example(2, "Heap Object"); // Object on heap
    
    // Using heap memory
    cout << "Heap integer: " << *heapInt << endl;
    cout << "Heap double: " << *heapDouble << endl;
    cout << "Heap char: " << *heapChar << endl;
    cout << "Heap array[2]: " << heapArray[2] << endl;
    heapObject->display();
    
    // MUST manually deallocate heap memory
    delete heapInt;
    delete heapDouble;
    delete heapChar;
    delete[] heapArray;  // Use delete[] for arrays
    delete heapObject;
    
    cout << "Heap memory manually deallocated\n";
}

void performanceComparison() {
    cout << "\n=== PERFORMANCE COMPARISON ===\n";
    
    const int SIZE = 1000000;
    
    // Stack allocation timing
    auto start = high_resolution_clock::now();
    for (int i = 0; i < 100; i++) {
        int stackArray[1000];  // Stack allocation
        // Use the array to prevent optimization
        for (int j = 0; j < 1000; j++) {
            stackArray[j] = j;
        }
    }
    auto stop = high_resolution_clock::now();
    auto stackDuration = duration_cast(stop - start);
    
    // Heap allocation timing
    start = high_resolution_clock::now();
    for (int i = 0; i < 100; i++) {
        int* heapArray = new int[1000];  // Heap allocation
        // Use the array
        for (int j = 0; j < 1000; j++) {
            heapArray[j] = j;
        }
        delete[] heapArray;  // Must deallocate
    }
    stop = high_resolution_clock::now();
    auto heapDuration = duration_cast(stop - start);
    
    cout << "Stack allocation time: " << stackDuration.count() << " microseconds\n";
    cout << "Heap allocation time: " << heapDuration.count() << " microseconds\n";
    cout << "Heap is " << (double)heapDuration.count() / stackDuration.count() 
         << " times slower than stack\n";
}

void stackOverflowDemo() {
    cout << "\n=== STACK OVERFLOW DEMONSTRATION ===\n";
    
    // WARNING: This may crash your program!
    // Uncomment to see stack overflow
    /*
    void causeOverflow(int depth) {
        char largeArray[100000];  // Large stack allocation
        cout << "Depth: " << depth << endl;
        causeOverflow(depth + 1);  // Recursive call
    }
    
    causeOverflow(1);
    */
    
    cout << "Stack overflow occurs when stack memory is exhausted\n";
    cout << "Common causes: deep recursion, large local arrays\n";
}

void memoryLeakDemo() {
    cout << "\n=== MEMORY LEAK DEMONSTRATION ===\n";
    
    // Memory leak example
    for (int i = 0; i < 10; i++) {
        int* leak = new int(i);  // Allocated but never deleted
        // No delete - MEMORY LEAK!
        // delete leak;  // Uncomment to fix
    }
    
    cout << "10 integers leaked (40 bytes)\n";
    cout << "In real programs, this can cause crashes over time\n";
}

int main() {
    cout << "=== STACK VS HEAP MEMORY IN C++ ===\n";
    
    demonstrateStack();
    demonstrateHeap();
    performanceComparison();
    stackOverflowDemo();
    memoryLeakDemo();
    
    return 0;
}
When to Use Stack
  • Small, short-lived variables
  • Known, fixed size at compile time
  • Local variables in functions
  • Temporary calculation results
  • Function parameters and return values
When to Use Heap
  • Large data structures
  • Size unknown at compile time
  • Objects that outlive their creating function
  • Shared data between functions/threads
  • Polymorphic objects (base class pointers)

2. Dynamic Memory Allocation: new and delete

// Dynamic allocation syntax:
type* pointer = new type; // Single object
type* array = new type[size]; // Array of objects

// Deallocation syntax:
delete pointer; // Single object
delete[] array; // Array of objects

// Placement new (advanced):
type* ptr = new(address) type; // Construct at specific address

Complete Guide to new and delete Operators

new and delete Operator Examples
#include <iostream>
#include <cstring>  // For memset
using namespace std;

class DynamicClass {
private:
    int id;
    char* buffer;
public:
    DynamicClass(int i, const char* text) : id(i) {
        buffer = new char[strlen(text) + 1];
        strcpy(buffer, text);
        cout << "DynamicClass " << id << " constructed: " << buffer << endl;
    }
    
    ~DynamicClass() {
        cout << "DynamicClass " << id << " destroyed: " << buffer << endl;
        delete[] buffer;  // Clean up internal dynamic memory
    }
    
    void display() const {
        cout << "ID: " << id << ", Text: " << buffer << endl;
    }
    
    // Copy constructor (deep copy)
    DynamicClass(const DynamicClass& other) : id(other.id) {
        buffer = new char[strlen(other.buffer) + 1];
        strcpy(buffer, other.buffer);
        cout << "DynamicClass " << id << " copy constructed" << endl;
    }
    
    // Assignment operator (deep copy)
    DynamicClass& operator=(const DynamicClass& other) {
        if (this != &other) {
            delete[] buffer;  // Free existing memory
            id = other.id;
            buffer = new char[strlen(other.buffer) + 1];
            strcpy(buffer, other.buffer);
            cout << "DynamicClass " << id << " assigned" << endl;
        }
        return *this;
    }
};

int main() {
    cout << "=== DYNAMIC MEMORY ALLOCATION: new AND delete ===\n\n";
    
    // 1. Basic dynamic allocation
    cout << "1. Basic Dynamic Allocation:" << endl;
    cout << "-----------------------------" << endl;
    
    // Single object allocation
    int* singleInt = new int(42);
    cout << "Single integer: " << *singleInt << " at address: " << singleInt << endl;
    
    // Single object with constructor
    DynamicClass* singleObj = new DynamicClass(1, "Hello Dynamic");
    singleObj->display();
    
    // Array allocation
    int* intArray = new int[5];
    for (int i = 0; i < 5; i++) {
        intArray[i] = i * 10;
    }
    cout << "Int array: ";
    for (int i = 0; i < 5; i++) {
        cout << intArray[i] << " ";
    }
    cout << endl;
    
    // Array of objects
    DynamicClass* objArray = new DynamicClass[3] {
        DynamicClass(2, "First"),
        DynamicClass(3, "Second"),
        DynamicClass(4, "Third")
    };
    
    // 2. Proper deallocation
    cout << "\n2. Proper Deallocation:" << endl;
    cout << "------------------------" << endl;
    
    delete singleInt;      // Single object
    delete singleObj;      // Single object with destructor
    delete[] intArray;     // Array of primitives
    delete[] objArray;     // Array of objects (calls destructors)
    
    cout << "All memory properly deallocated\n";
    
    // 3. Common mistakes and proper patterns
    cout << "\n3. Common Patterns and Mistakes:" << endl;
    cout << "----------------------------------" << endl;
    
    // A. Always check allocation success
    int* largeArray = nullptr;
    try {
        largeArray = new int[1000000000];  // Very large allocation
        cout << "Large array allocated successfully\n";
    } catch (const bad_alloc& e) {
        cout << "Memory allocation failed: " << e.what() << endl;
        largeArray = nullptr;
    }
    
    // B. Initialize dynamically allocated memory
    int* uninitialized = new int;  // Contains garbage
    int* initialized = new int();   // Value-initialized to 0
    int* zeroed = new int{};        // Also value-initialized to 0 (C++11)
    
    cout << "Uninitialized: " << *uninitialized << " (garbage)" << endl;
    cout << "Initialized: " << *initialized << " (0)" << endl;
    cout << "Zeroed: " << *zeroed << " (0)" << endl;
    
    delete uninitialized;
    delete initialized;
    delete zeroed;
    
    // C. Array initialization
    int* arr1 = new int[5];           // Uninitialized
    int* arr2 = new int[5]();         // All zeros
    int* arr3 = new int[5]{1, 2, 3};  // Partial initialization (rest zeros)
    
    cout << "Array 2 (all zeros): ";
    for (int i = 0; i < 5; i++) cout << arr2[i] << " ";
    cout << "\nArray 3 (partial init): ";
    for (int i = 0; i < 5; i++) cout << arr3[i] << " ";
    cout << endl;
    
    delete[] arr1;
    delete[] arr2;
    delete[] arr3;
    
    // 4. Dynamic 2D arrays
    cout << "\n4. Dynamic 2D Arrays:" << endl;
    cout << "---------------------" << endl;
    
    int rows = 3, cols = 4;
    
    // Method 1: Array of pointers (jagged array)
    int** matrix1 = new int*[rows];
    for (int i = 0; i < rows; i++) {
        matrix1[i] = new int[cols];
        for (int j = 0; j < cols; j++) {
            matrix1[i][j] = i * cols + j + 1;
        }
    }
    
    cout << "Method 1 (Array of pointers):\n";
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            cout << matrix1[i][j] << "\t";
        }
        cout << endl;
    }
    
    // Method 2: Single block (contiguous memory)
    int* matrix2 = new int[rows * cols];
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix2[i * cols + j] = i * cols + j + 1;
        }
    }
    
    cout << "\nMethod 2 (Single block):\n";
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            cout << matrix2[i * cols + j] << "\t";
        }
        cout << endl;
    }
    
    // Proper cleanup for 2D arrays
    for (int i = 0; i < rows; i++) {
        delete[] matrix1[i];
    }
    delete[] matrix1;
    delete[] matrix2;
    
    // 5. Placement new (advanced)
    cout << "\n5. Placement New (Advanced):" << endl;
    cout << "---------------------------" << endl;
    
    // Allocate raw memory
    void* rawMemory = operator new(sizeof(DynamicClass));
    
    // Construct object in pre-allocated memory
    DynamicClass* placedObj = new(rawMemory) DynamicClass(5, "Placement New");
    placedObj->display();
    
    // Manually call destructor
    placedObj->~DynamicClass();
    
    // Free raw memory
    operator delete(rawMemory);
    
    // 6. Custom new/delete operators
    cout << "\n6. Custom Memory Management:" << endl;
    cout << "-----------------------------" << endl;
    
    class CustomAllocator {
    public:
        static void* operator new(size_t size) {
            cout << "Custom new called for " << size << " bytes\n";
            void* p = malloc(size);
            if (!p) throw bad_alloc();
            return p;
        }
        
        static void operator delete(void* p) {
            cout << "Custom delete called\n";
            free(p);
        }
        
        static void* operator new[](size_t size) {
            cout << "Custom new[] called for " << size << " bytes\n";
            void* p = malloc(size);
            if (!p) throw bad_alloc();
            return p;
        }
        
        static void operator delete[](void* p) {
            cout << "Custom delete[] called\n";
            free(p);
        }
    };
    
    CustomAllocator* customObj = new CustomAllocator();
    delete customObj;
    
    CustomAllocator* customArray = new CustomAllocator[3];
    delete[] customArray;
    
    return 0;
}
Critical Rules for new/delete:
  • Always match new with delete, new[] with delete[]
  • Never delete memory twice (double-free error)
  • Set pointer to nullptr after deletion
  • Check for allocation failure (use try-catch or std::nothrow)
  • Initialize dynamically allocated memory
  • Implement Rule of Three/Five for classes with dynamic memory

3. Memory Leaks: Detection and Prevention

Memory Leak Examples and Solutions
#include <iostream>
#include <vector>
#include <memory>
#include <cstdlib>
#include <cstring>
using namespace std;

// Class that demonstrates potential memory leaks
class LeakyClass {
private:
    char* data;
    int* array;
    int size;
public:
    LeakyClass(int s) : size(s) {
        data = new char[100];
        array = new int[size];
        cout << "LeakyClass allocated " << (100 + size * sizeof(int)) << " bytes\n";
    }
    
    // PROBLEM: No destructor to clean up memory!
    // ~LeakyClass() { delete[] data; delete[] array; }
    
    // PROBLEM: No copy constructor or assignment operator
    // This will cause double-free if copies are made
};

// Proper class with complete memory management
class SafeClass {
private:
    char* data;
    int* array;
    int size;
public:
    SafeClass(int s) : size(s) {
        data = new char[100];
        array = new int[size];
        cout << "SafeClass allocated memory\n";
    }
    
    // Destructor - essential!
    ~SafeClass() {
        delete[] data;
        delete[] array;
        cout << "SafeClass freed memory\n";
    }
    
    // Copy constructor (deep copy)
    SafeClass(const SafeClass& other) : size(other.size) {
        data = new char[100];
        memcpy(data, other.data, 100);
        array = new int[size];
        memcpy(array, other.array, size * sizeof(int));
        cout << "SafeClass copy constructed\n";
    }
    
    // Assignment operator
    SafeClass& operator=(const SafeClass& other) {
        if (this != &other) {
            delete[] data;
            delete[] array;
            
            size = other.size;
            data = new char[100];
            memcpy(data, other.data, 100);
            array = new int[size];
            memcpy(array, other.array, size * sizeof(int));
            cout << "SafeClass assigned\n";
        }
        return *this;
    }
    
    // Move constructor (C++11)
    SafeClass(SafeClass&& other) noexcept 
        : data(other.data), array(other.array), size(other.size) {
        other.data = nullptr;
        other.array = nullptr;
        other.size = 0;
        cout << "SafeClass move constructed\n";
    }
    
    // Move assignment (C++11)
    SafeClass& operator=(SafeClass&& other) noexcept {
        if (this != &other) {
            delete[] data;
            delete[] array;
            
            data = other.data;
            array = other.array;
            size = other.size;
            
            other.data = nullptr;
            other.array = nullptr;
            other.size = 0;
            cout << "SafeClass move assigned\n";
        }
        return *this;
    }
};

void demonstrateCommonLeaks() {
    cout << "\n=== COMMON MEMORY LEAK PATTERNS ===\n";
    
    // 1. Simple allocation without deallocation
    cout << "1. Simple Leak:\n";
    int* leak1 = new int(42);
    // Oops! Forgot to delete leak1
    
    // 2. Early return or exception
    cout << "\n2. Early Return Leak:\n";
    auto riskyFunction = []() -> int* {
        int* ptr = new int[100];
        
        // Early return - memory leak!
        // if (someCondition) return nullptr;
        
        // Exception - memory leak!
        // throw runtime_error("Error!");
        
        return ptr;
    };
    
    int* result = riskyFunction();
    // Should delete result here...
    
    // 3. Reassignment without deletion
    cout << "\n3. Reassignment Leak:\n";
    int* ptr = new int(10);
    ptr = new int(20);  // First allocation leaked!
    delete ptr;  // Only deletes second allocation
    
    // 4. Array delete mismatch
    cout << "\n4. Delete Mismatch:\n";
    int* array = new int[10];
    // delete array;  // WRONG! Should be delete[] array;
    delete[] array;  // Correct
    
    // 5. Circular references (with raw pointers)
    cout << "\n5. Circular Reference:\n";
    class Node {
    public:
        Node* next;
        Node() : next(nullptr) {}
    };
    
    Node* a = new Node();
    Node* b = new Node();
    a->next = b;
    b->next = a;  // Circular reference
    // Both nodes leaked even if we delete one
    
    // 6. Collection of pointers
    cout << "\n6. Collection Leak:\n";
    vector pointerCollection;
    for (int i = 0; i < 10; i++) {
        pointerCollection.push_back(new int(i));
    }
    // Must delete all elements before vector is destroyed
    for (auto p : pointerCollection) {
        delete p;
    }
}

void demonstrateMemoryTools() {
    cout << "\n=== MEMORY LEAK DETECTION TOOLS ===\n";
    
    #ifdef _MSC_VER
    // Visual Studio memory leak detection
    cout << "Visual Studio: Use _CrtDumpMemoryLeaks()\n";
    // #define _CRTDBG_MAP_ALLOC
    // #include <crtdbg.h>
    // _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
    #endif
    
    #ifdef __GNUC__
    // GCC/Clang memory tools
    cout << "GCC/Clang: Use valgrind or AddressSanitizer\n";
    cout << "Compile with: -fsanitize=address -g\n";
    #endif
    
    // Manual tracking
    cout << "\nManual Tracking Techniques:\n";
    
    static int allocations = 0;
    static int deallocations = 0;
    
    class TrackedObject {
    public:
        TrackedObject() { allocations++; }
        ~TrackedObject() { deallocations++; }
    };
    
    TrackedObject* obj1 = new TrackedObject();
    TrackedObject* obj2 = new TrackedObject();
    
    delete obj1;
    // Forgot to delete obj2
    
    cout << "Allocations: " << allocations << endl;
    cout << "Deallocations: " << deallocations << endl;
    cout << "Potential leaks: " << (allocations - deallocations) << endl;
}

void demonstrateSmartPointers() {
    cout << "\n=== SMART POINTERS FOR MEMORY SAFETY ===\n";
    
    // 1. unique_ptr - exclusive ownership
    cout << "1. unique_ptr (no leaks, automatic cleanup):\n";
    {
        unique_ptr uptr1 = make_unique(42);
        unique_ptr uptrArray = make_unique(10);
        
        // No need to delete - automatically deleted when out of scope
        cout << "unique_ptr value: " << *uptr1 << endl;
    }  // Memory automatically freed here
    
    // 2. shared_ptr - shared ownership
    cout << "\n2. shared_ptr (reference counting):\n";
    {
        shared_ptr sptr1 = make_shared(100);
        {
            shared_ptr sptr2 = sptr1;  // Reference count: 2
            cout << "Use count inside inner scope: " << sptr1.use_count() << endl;
        }  // sptr2 destroyed, reference count: 1
        
        cout << "Use count outside inner scope: " << sptr1.use_count() << endl;
    }  // sptr1 destroyed, memory freed (reference count: 0)
    
    // 3. weak_ptr - break circular references
    cout << "\n3. weak_ptr (break circular references):\n";
    class CyclicNode {
    public:
        shared_ptr partner;
        weak_ptr weakPartner;  // Use weak_ptr to break cycles
        
        CyclicNode(string n) : name(n) {
            cout << "Node " << name << " created\n";
        }
        
        ~CyclicNode() {
            cout << "Node " << name << " destroyed\n";
        }
        
        string name;
    };
    
    {
        auto nodeA = make_shared("A");
        auto nodeB = make_shared("B");
        
        // PROBLEM: Circular reference with shared_ptr
        // nodeA->partner = nodeB;
        // nodeB->partner = nodeA;
        // Both nodes leak!
        
        // SOLUTION: Use weak_ptr
        nodeA->weakPartner = nodeB;
        nodeB->weakPartner = nodeA;
        
        // Access through weak_ptr
        if (auto sharedB = nodeA->weakPartner.lock()) {
            cout << "Accessed B through weak_ptr: " << sharedB->name << endl;
        }
    }  // Both nodes properly destroyed
    
    cout << "\nSmart pointers automatically prevent memory leaks!\n";
}

void demonstrateRAII() {
    cout << "\n=== RAII PATTERN ===\n";
    cout << "Resource Acquisition Is Initialization\n";
    
    // RAII wrapper for dynamic array
    class IntArray {
    private:
        int* data;
        size_t size;
    public:
        IntArray(size_t n) : size(n) {
            data = new int[n];
            cout << "IntArray allocated " << n << " integers\n";
        }
        
        ~IntArray() {
            delete[] data;
            cout << "IntArray deallocated\n";
        }
        
        // No copy (or implement deep copy)
        IntArray(const IntArray&) = delete;
        IntArray& operator=(const IntArray&) = delete;
        
        // Allow move
        IntArray(IntArray&& other) noexcept : data(other.data), size(other.size) {
            other.data = nullptr;
            other.size = 0;
        }
        
        int& operator[](size_t index) {
            return data[index];
        }
        
        size_t getSize() const { return size; }
    };
    
    {
        IntArray arr(10);
        for (size_t i = 0; i < arr.getSize(); i++) {
            arr[i] = i * 10;
        }
        
        cout << "RAII array elements: ";
        for (size_t i = 0; i < arr.getSize(); i++) {
            cout << arr[i] << " ";
        }
        cout << endl;
        
        // No need to manually delete - destructor handles it
    }  // arr automatically destroyed here
    
    cout << "RAII ensures resources are always properly released\n";
}

int main() {
    cout << "=== MEMORY LEAKS: DETECTION AND PREVENTION ===\n";
    
    demonstrateCommonLeaks();
    demonstrateMemoryTools();
    demonstrateSmartPointers();
    demonstrateRAII();
    
    // Final cleanup reminder
    cout << "\n=== BEST PRACTICES SUMMARY ===\n";
    cout << "1. Prefer stack allocation when possible\n";
    cout << "2. Use smart pointers (unique_ptr, shared_ptr)\n";
    cout << "3. Implement RAII for resource management\n";
    cout << "4. Always match new with delete, new[] with delete[]\n";
    cout << "5. Use tools (valgrind, AddressSanitizer) to detect leaks\n";
    cout << "6. Follow Rule of Three/Five for classes with resources\n";
    
    return 0;
}
Prevention Techniques
  • Use smart pointers
  • Implement RAII pattern
  • Follow Rule of Three/Five
  • Prefer stack allocation
  • Use standard containers
Detection Tools
  • Valgrind (Linux/Mac)
  • AddressSanitizer
  • Visual Studio Debugger
  • Manual tracking counters
  • Memory profilers
Common Leak Sources
  • Missing delete statements
  • Early returns/exceptions
  • Circular references
  • Wrong delete (delete vs delete[])
  • Collection of raw pointers

4. Smart Pointers for Memory Management

Smart Pointers for Dynamic Memory Management
#include <iostream>
#include <memory>
#include <vector>
#include <string>
using namespace std;

class Resource {
private:
    string name;
    int* data;
    size_t size;
public:
    Resource(string n, size_t s) : name(n), size(s) {
        data = new int[size];
        for (size_t i = 0; i < size; i++) {
            data[i] = i * 10;
        }
        cout << "Resource '" << name << "' created with " << size << " elements\n";
    }
    
    ~Resource() {
        delete[] data;
        cout << "Resource '" << name << "' destroyed\n";
    }
    
    void display() const {
        cout << name << ": [";
        for (size_t i = 0; i < min(size, size_t(5)); i++) {
            cout << data[i];
            if (i < min(size, size_t(5)) - 1) cout << ", ";
        }
        if (size > 5) cout << ", ...";
        cout << "]\n";
    }
    
    string getName() const { return name; }
};

void demonstrateUniquePtr() {
    cout << "\n=== unique_ptr: EXCLUSIVE OWNERSHIP ===\n";
    
    // 1. Creating unique_ptr
    unique_ptr res1 = make_unique("Unique1", 10);
    unique_ptr res2(new Resource("Unique2", 5));  // Alternative
    
    // 2. Accessing and using
    res1->display();
    cout << "Resource name: " << res1->getName() << endl;
    
    // 3. Ownership transfer (move semantics)
    cout << "\nOwnership transfer:\n";
    unique_ptr res3 = move(res1);  // res1 becomes empty
    if (!res1) {
        cout << "res1 is now empty\n";
    }
    res3->display();
    
    // 4. Custom deleters
    cout << "\nCustom deleters:\n";
    auto customDeleter = [](Resource* r) {
        cout << "Custom deleter called for: " << r->getName() << endl;
        delete r;
    };
    
    unique_ptr res4(
        new Resource("CustomDeleted", 3), 
        customDeleter
    );
    
    // 5. Arrays with unique_ptr
    cout << "\nDynamic arrays with unique_ptr:\n";
    unique_ptr resourceArray(new Resource[3]{
        Resource("Array1", 2),
        Resource("Array2", 3),
        Resource("Array3", 4)
    });
    
    for (int i = 0; i < 3; i++) {
        resourceArray[i].display();
    }
    
    // 6. Returning unique_ptr from functions
    auto createResource = [](string name, size_t size) -> unique_ptr {
        return make_unique(name, size);
    };
    
    auto returnedResource = createResource("Returned", 8);
    returnedResource->display();
}

void demonstrateSharedPtr() {
    cout << "\n=== shared_ptr: SHARED OWNERSHIP ===\n";
    
    // 1. Creating shared_ptr
    shared_ptr shared1 = make_shared("Shared1", 10);
    cout << "Initial use count: " << shared1.use_count() << endl;
    
    // 2. Sharing ownership
    shared_ptr shared2 = shared1;
    shared_ptr shared3 = shared2;
    
    cout << "After sharing, use count: " << shared1.use_count() << endl;
    
    // 3. All objects can use the resource
    shared1->display();
    shared2->display();
    shared3->display();
    
    // 4. Reset and ownership release
    cout << "\nResetting shared pointers:\n";
    shared2.reset();  // Release ownership
    cout << "After reset shared2, use count: " << shared1.use_count() << endl;
    
    shared1.reset();
    cout << "After reset shared1, use count: " << shared3.use_count() << endl;
    
    // 5. make_shared efficiency
    cout << "\nmake_shared efficiency:\n";
    // make_shared allocates object and control block together (more efficient)
    auto efficient = make_shared("Efficient", 5);
    
    // 6. shared_ptr with arrays (C++17)
    cout << "\nshared_ptr with arrays (C++17):\n";
    shared_ptr sharedArray(new Resource[2]{
        Resource("SharedArray1", 3),
        Resource("SharedArray2", 4)
    });
    
    for (int i = 0; i < 2; i++) {
        sharedArray[i].display();
    }
}

void demonstrateWeakPtr() {
    cout << "\n=== weak_ptr: NON-OWNING REFERENCES ===\n";
    
    // 1. Creating weak_ptr from shared_ptr
    shared_ptr shared = make_shared("ForWeakPtr", 6);
    weak_ptr weak = shared;
    
    cout << "shared use count: " << shared.use_count() << endl;
    
    // 2. Checking and accessing weak_ptr
    if (auto locked = weak.lock()) {
        cout << "Successfully locked weak_ptr: ";
        locked->display();
        cout << "Now use count: " << shared.use_count() << endl;
    }
    
    // 3. Expired weak_ptr
    shared.reset();
    cout << "\nAfter reset shared:\n";
    cout << "shared use count: " << shared.use_count() << endl;
    
    if (weak.expired()) {
        cout << "weak_ptr is expired\n";
    }
    
    // 4. Practical example: Cache with weak_ptr
    cout << "\nPractical example: Resource Cache\n";
    
    class ResourceCache {
    private:
        vector> cache;
    public:
        shared_ptr getResource(string name, size_t size) {
            // Check cache first
            for (auto& weakRes : cache) {
                if (auto sharedRes = weakRes.lock()) {
                    if (sharedRes->getName() == name) {
                        cout << "Cache hit: " << name << endl;
                        return sharedRes;
                    }
                }
            }
            
            // Not in cache, create new
            cout << "Cache miss, creating: " << name << endl;
            auto newResource = make_shared(name, size);
            cache.push_back(newResource);
            return newResource;
        }
        
        void cleanupExpired() {
            // Remove expired weak_ptrs
            cache.erase(
                remove_if(cache.begin(), cache.end(),
                    [](const weak_ptr& wp) { return wp.expired(); }),
                cache.end()
            );
        }
    };
    
    ResourceCache cache;
    auto res1 = cache.getResource("Cached1", 5);
    auto res2 = cache.getResource("Cached2", 3);
    auto res3 = cache.getResource("Cached1", 5);  // Should be cache hit
    
    // Release some resources
    res1.reset();
    res2.reset();
    
    cache.cleanupExpired();
}

void demonstrateOwnershipPatterns() {
    cout << "\n=== OWNERSHIP PATTERNS AND BEST PRACTICES ===\n";
    
    // 1. Factory function returning unique_ptr
    cout << "1. Factory functions:\n";
    auto createUnique = []() -> unique_ptr {
        return make_unique("FactoryCreated", 10);
    };
    
    auto product = createUnique();
    product->display();
    
    // 2. Transferring ownership to containers
    cout << "\n2. Containers of unique_ptr:\n";
    vector> resources;
    resources.push_back(make_unique("Vector1", 3));
    resources.push_back(make_unique("Vector2", 4));
    resources.push_back(make_unique("Vector3", 5));
    
    // Must use move when adding to vector
    auto temp = make_unique("Temp", 2);
    resources.push_back(move(temp));
    
    for (auto& res : resources) {
        res->display();
    }
    
    // 3. Shared ownership in multi-threaded scenarios
    cout << "\n3. Thread-safe shared ownership:\n";
    // shared_ptr reference counting is thread-safe
    // But accessing the object itself needs synchronization
    
    // 4. Converting between smart pointer types
    cout << "\n4. Smart pointer conversion:\n";
    
    // unique_ptr to shared_ptr (transfer ownership)
    unique_ptr uniqueRes = make_unique("Convertible", 5);
    shared_ptr sharedRes = move(uniqueRes);
    cout << "Converted unique_ptr to shared_ptr\n";
    
    // Cannot convert shared_ptr to unique_ptr (would break shared ownership)
    
    // 5. Polymorphic objects with smart pointers
    cout << "\n5. Polymorphic objects:\n";
    
    class Base {
    public:
        virtual void display() const {
            cout << "Base class\n";
        }
        virtual ~Base() = default;
    };
    
    class Derived : public Base {
    public:
        void display() const override {
            cout << "Derived class\n";
        }
    };
    
    unique_ptr poly = make_unique();
    poly->display();
}

void demonstrateMemorySafety() {
    cout << "\n=== MEMORY SAFETY WITH SMART POINTERS ===\n";
    
    // Comparison: Raw pointers vs Smart pointers
    
    // 1. Memory leak prevention
    cout << "1. Automatic cleanup:\n";
    {
        // Raw pointer - potential leak
        Resource* raw = new Resource("RawPointer", 5);
        // Must remember to delete!
        delete raw;
    }
    
    {
        // Smart pointer - no leak
        auto smart = make_unique("SmartPointer", 5);
        // Automatically deleted when out of scope
    }
    
    // 2. Exception safety
    cout << "\n2. Exception safety:\n";
    auto riskyOperation = []() {
        auto resource = make_unique("ExceptionSafe", 3);
        
        // If exception occurs here...
        throw runtime_error("Something went wrong!");
        
        // unique_ptr will still clean up the resource
    };
    
    try {
        riskyOperation();
    } catch (const exception& e) {
        cout << "Caught exception: " << e.what() << endl;
        cout << "Resource was still properly cleaned up!\n";
    }
    
    // 3. Dangling pointer prevention
    cout << "\n3. No dangling pointers:\n";
    shared_ptr shared;
    
    {
        auto temp = make_shared("Temporary", 2);
        shared = temp;  // shared maintains reference
    }  // temp goes out of scope, but resource persists
    
    shared->display();  // Safe! Resource still exists
    
    // 4. Custom deleters for special resources
    cout << "\n4. Custom deleters for non-heap resources:\n";
    
    // File handle example
    FILE* file = fopen("test.txt", "w");
    if (file) {
        unique_ptr filePtr(file, &fclose);
        fprintf(filePtr.get(), "Hello, World!\n");
        // File automatically closed when filePtr goes out of scope
        cout << "File automatically closed\n";
    }
}

int main() {
    cout << "=== SMART POINTERS FOR DYNAMIC MEMORY MANAGEMENT ===\n";
    
    demonstrateUniquePtr();
    demonstrateSharedPtr();
    demonstrateWeakPtr();
    demonstrateOwnershipPatterns();
    demonstrateMemorySafety();
    
    cout << "\n=== SMART POINTER BEST PRACTICES ===\n";
    cout << "1. Prefer unique_ptr for exclusive ownership\n";
    cout << "2. Use shared_ptr only when shared ownership is needed\n";
    cout << "3. Use weak_ptr to break circular references\n";
    cout << "4. Always use make_unique and make_shared when possible\n";
    cout << "5. Never mix raw pointers and smart pointers for ownership\n";
    cout << "6. Use custom deleters for special resources\n";
    
    return 0;
}

Modern C++ Memory Management Philosophy

In modern C++, avoid manual memory management with raw new/delete. Instead, use smart pointers and RAII. Prefer stack allocation for small, short-lived objects. Use unique_ptr for exclusive ownership, shared_ptr only when shared ownership is truly needed, and weak_ptr to observe shared resources without ownership.

5. Practical Applications and Patterns

Dynamic Data Structures Implementation

Dynamic Data Structures with Memory Management
#include <iostream>
#include <memory>
#include <stdexcept>
using namespace std;

// 1. Dynamic Array (Vector-like) with RAII
template
class DynamicArray {
private:
    unique_ptr data;
    size_t capacity;
    size_t size;
    
    void resize(size_t newCapacity) {
        unique_ptr newData = make_unique(newCapacity);
        for (size_t i = 0; i < size; i++) {
            newData[i] = move(data[i]);
        }
        data = move(newData);
        capacity = newCapacity;
    }
    
public:
    DynamicArray() : capacity(10), size(0) {
        data = make_unique(capacity);
    }
    
    DynamicArray(size_t initialCapacity) : capacity(initialCapacity), size(0) {
        if (initialCapacity == 0) capacity = 1;
        data = make_unique(capacity);
    }
    
    ~DynamicArray() {
        // unique_ptr automatically deallocates
    }
    
    // No copy (or implement deep copy)
    DynamicArray(const DynamicArray&) = delete;
    DynamicArray& operator=(const DynamicArray&) = delete;
    
    // Move operations
    DynamicArray(DynamicArray&& other) noexcept 
        : data(move(other.data)), capacity(other.capacity), size(other.size) {
        other.capacity = 0;
        other.size = 0;
    }
    
    DynamicArray& operator=(DynamicArray&& other) noexcept {
        if (this != &other) {
            data = move(other.data);
            capacity = other.capacity;
            size = other.size;
            other.capacity = 0;
            other.size = 0;
        }
        return *this;
    }
    
    void push_back(const T& value) {
        if (size >= capacity) {
            resize(capacity * 2);
        }
        data[size++] = value;
    }
    
    void push_back(T&& value) {
        if (size >= capacity) {
            resize(capacity * 2);
        }
        data[size++] = move(value);
    }
    
    T& operator[](size_t index) {
        if (index >= size) {
            throw out_of_range("Index out of bounds");
        }
        return data[index];
    }
    
    const T& operator[](size_t index) const {
        if (index >= size) {
            throw out_of_range("Index out of bounds");
        }
        return data[index];
    }
    
    size_t getSize() const { return size; }
    size_t getCapacity() const { return capacity; }
    
    void shrink_to_fit() {
        if (size < capacity) {
            resize(size);
        }
    }
};

// 2. Linked List with smart pointers
template
class LinkedList {
private:
    struct Node {
        T data;
        unique_ptr next;
        
        Node(T val) : data(move(val)), next(nullptr) {}
    };
    
    unique_ptr head;
    Node* tail;
    size_t listSize;
    
public:
    LinkedList() : head(nullptr), tail(nullptr), listSize(0) {}
    
    ~LinkedList() {
        // Recursive destruction with unique_ptr will automatically clean up
    }
    
    void push_back(const T& value) {
        auto newNode = make_unique(value);
        
        if (!head) {
            head = move(newNode);
            tail = head.get();
        } else {
            tail->next = move(newNode);
            tail = tail->next.get();
        }
        listSize++;
    }
    
    void push_front(const T& value) {
        auto newNode = make_unique(value);
        newNode->next = move(head);
        head = move(newNode);
        
        if (!tail) {
            tail = head.get();
        }
        listSize++;
    }
    
    void display() const {
        Node* current = head.get();
        cout << "List: ";
        while (current) {
            cout << current->data << " -> ";
            current = current->next.get();
        }
        cout << "nullptr\n";
    }
    
    size_t size() const { return listSize; }
    
    bool empty() const { return listSize == 0; }
    
    // Iterator support (simplified)
    class Iterator {
    private:
        Node* current;
    public:
        Iterator(Node* node) : current(node) {}
        
        T& operator*() { return current->data; }
        Iterator& operator++() {
            current = current->next.get();
            return *this;
        }
        bool operator!=(const Iterator& other) const {
            return current != other.current;
        }
    };
    
    Iterator begin() { return Iterator(head.get()); }
    Iterator end() { return Iterator(nullptr); }
};

// 3. Memory Pool for efficient allocation
class MemoryPool {
private:
    struct Block {
        unique_ptr memory;
        bool isFree;
        
        Block(size_t size) : memory(make_unique(size)), isFree(true) {}
    };
    
    vector> blocks;
    size_t blockSize;
    size_t poolSize;
    
public:
    MemoryPool(size_t blockSize, size_t numBlocks) 
        : blockSize(blockSize), poolSize(numBlocks) {
        for (size_t i = 0; i < numBlocks; i++) {
            blocks.push_back(make_unique(blockSize));
        }
    }
    
    void* allocate() {
        for (auto& block : blocks) {
            if (block->isFree) {
                block->isFree = false;
                return block->memory.get();
            }
        }
        return nullptr;  // Pool exhausted
    }
    
    void deallocate(void* ptr) {
        for (auto& block : blocks) {
            if (block->memory.get() == ptr) {
                block->isFree = true;
                return;
            }
        }
        throw runtime_error("Pointer not from this pool");
    }
    
    size_t getFreeBlocks() const {
        size_t count = 0;
        for (auto& block : blocks) {
            if (block->isFree) count++;
        }
        return count;
    }
};

// 4. Object Pool for reusing objects
template
class ObjectPool {
private:
    vector> pool;
    vector freeList;
    
public:
    ObjectPool(size_t initialSize) {
        for (size_t i = 0; i < initialSize; i++) {
            pool.push_back(make_unique());
            freeList.push_back(pool.back().get());
        }
    }
    
    T* acquire() {
        if (freeList.empty()) {
            // Expand pool
            pool.push_back(make_unique());
            freeList.push_back(pool.back().get());
        }
        
        T* obj = freeList.back();
        freeList.pop_back();
        return obj;
    }
    
    void release(T* obj) {
        freeList.push_back(obj);
    }
    
    size_t getFreeCount() const {
        return freeList.size();
    }
    
    size_t getTotalCount() const {
        return pool.size();
    }
};

// Example usage
class GameCharacter {
private:
    string name;
    int health;
    
public:
    GameCharacter(string n = "", int h = 100) : name(n), health(h) {}
    
    void takeDamage(int damage) {
        health -= damage;
        if (health < 0) health = 0;
    }
    
    bool isAlive() const { return health > 0; }
    
    void display() const {
        cout << name << " [HP: " << health << "]" << endl;
    }
};

void demonstrateDynamicArray() {
    cout << "=== DYNAMIC ARRAY IMPLEMENTATION ===\n";
    
    DynamicArray arr;
    
    cout << "Initial capacity: " << arr.getCapacity() << endl;
    
    for (int i = 0; i < 25; i++) {
        arr.push_back(i * 10);
    }
    
    cout << "After adding 25 elements:\n";
    cout << "Size: " << arr.getSize() << ", Capacity: " << arr.getCapacity() << endl;
    
    cout << "Elements: ";
    for (size_t i = 0; i < arr.getSize(); i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
    
    arr.shrink_to_fit();
    cout << "After shrink_to_fit:\n";
    cout << "Size: " << arr.getSize() << ", Capacity: " << arr.getCapacity() << endl;
}

void demonstrateLinkedList() {
    cout << "\n=== LINKED LIST WITH SMART POINTERS ===\n";
    
    LinkedList list;
    
    list.push_back("Hello");
    list.push_back("World");
    list.push_front("C++");
    list.push_back("Memory");
    list.push_back("Management");
    
    list.display();
    cout << "List size: " << list.size() << endl;
    
    cout << "Using iterators: ";
    for (const auto& item : list) {
        cout << item << " ";
    }
    cout << endl;
}

void demonstrateMemoryPool() {
    cout << "\n=== MEMORY POOL PATTERN ===\n";
    
    MemoryPool pool(256, 5);  // 5 blocks of 256 bytes each
    
    cout << "Free blocks initially: " << pool.getFreeBlocks() << endl;
    
    void* ptr1 = pool.allocate();
    void* ptr2 = pool.allocate();
    
    cout << "After allocating 2 blocks: " << pool.getFreeBlocks() << endl;
    
    pool.deallocate(ptr1);
    cout << "After deallocating 1 block: " << pool.getFreeBlocks() << endl;
    
    // Simulate usage
    if (ptr2) {
        int* numbers = static_cast(ptr2);
        for (int i = 0; i < 10; i++) {
            numbers[i] = i * 5;
        }
        
        cout << "Numbers in allocated block: ";
        for (int i = 0; i < 10; i++) {
            cout << numbers[i] << " ";
        }
        cout << endl;
    }
}

void demonstrateObjectPool() {
    cout << "\n=== OBJECT POOL FOR GAME CHARACTERS ===\n";
    
    ObjectPool characterPool(3);
    
    cout << "Pool created with " << characterPool.getTotalCount() 
         << " characters, " << characterPool.getFreeCount() << " free\n";
    
    // Acquire characters
    GameCharacter* hero1 = characterPool.acquire();
    *hero1 = GameCharacter("Hero1", 150);
    
    GameCharacter* hero2 = characterPool.acquire();
    *hero2 = GameCharacter("Hero2", 120);
    
    GameCharacter* enemy = characterPool.acquire();
    *enemy = GameCharacter("Enemy", 80);
    
    cout << "\nCharacters in play:\n";
    hero1->display();
    hero2->display();
    enemy->display();
    
    cout << "\nFree characters: " << characterPool.getFreeCount() << endl;
    
    // Release a character back to pool
    cout << "\nHero2 defeated, returning to pool...\n";
    characterPool.release(hero2);
    
    cout << "Free characters after release: " << characterPool.getFreeCount() << endl;
    
    // Acquire from pool again
    GameCharacter* newHero = characterPool.acquire();
    *newHero = GameCharacter("NewHero", 100);
    newHero->display();
}

int main() {
    cout << "=== PRACTICAL APPLICATIONS OF DYNAMIC MEMORY ===\n";
    
    demonstrateDynamicArray();
    demonstrateLinkedList();
    demonstrateMemoryPool();
    demonstrateObjectPool();
    
    cout << "\n=== KEY TAKEAWAYS ===\n";
    cout << "1. Use RAII for all resource management\n";
    cout << "2. Prefer standard containers (vector, list) when possible\n";
    cout << "3. For custom data structures, use smart pointers\n";
    cout << "4. Consider memory pools for performance-critical code\n";
    cout << "5. Object pools reduce allocation overhead for frequently created objects\n";
    
    return 0;
}

6. Best Practices and Performance Optimization

Practice Bad Example Good Example
Memory Allocation int* arr = new int[n]; vector<int> arr(n); or unique_ptr<int[]>
Ownership Transfer Resource* create() { return new Resource(); } unique_ptr<Resource> create() { return make_unique<Resource>(); }
Exception Safety void process() { Resource* r = new Resource(); /* might throw */ delete r; } void process() { auto r = make_unique<Resource>(); /* exception safe */ }
Array Management int** matrix = new int*[rows]; for(i) matrix[i] = new int[cols]; vector<vector<int>> matrix(rows, vector<int>(cols));
Resource Cleanup File* f = fopen(); /* ... */ fclose(f); unique_ptr<FILE, decltype(&fclose)> f(fopen(), &fclose);
Polymorphic Objects Base* obj = new Derived(); delete obj; unique_ptr<Base> obj = make_unique<Derived>();
Memory Management Best Practices
  • Prefer stack allocation for small, short-lived objects
  • Use smart pointers instead of raw new/delete
  • Follow RAII pattern for all resources
  • Implement Rule of Three/Five for resource-owning classes
  • Use standard library containers when possible
  • Always check allocation success
  • Profile before optimizing memory usage
Performance Considerations
  • Heap allocation is 100-1000x slower than stack
  • Memory fragmentation affects long-running programs
  • Cache locality is better with contiguous memory
  • Virtual memory overhead for large allocations
  • Smart pointers have small runtime overhead
  • Custom allocators can improve specific use cases

Memory Optimization Techniques

  • Pool Allocation: Pre-allocate objects of same size
  • Slab Allocation: Allocate in large blocks, manage internally
  • Arena Allocation: Allocate sequentially, free all at once
  • Memory Mapping: Use mmap for very large allocations
  • Custom Allocators: Implement allocators for specific patterns
  • Object Reuse: Object pools for frequently created/destroyed objects

Quick Quiz: Test Your Knowledge

What happens when this code executes?

void process() {
  int* ptr = new int[100];
  if (errorCondition) return;
  delete[] ptr;
}
A) Memory leak if errorCondition is true
B) Always deallocates memory properly
C) Stack overflow
D) Double free error

Summary & Key Takeaways

Stack Memory
  • Automatic allocation/deallocation
  • Very fast (CPU instructions)
  • Limited size (1-8 MB typically)
  • Function/scope lifetime
  • Perfect for local variables
Heap Memory
  • Manual allocation (new/malloc)
  • Manual deallocation (delete/free)
  • Large size (limited by OS/RAM)
  • Programmer-controlled lifetime
  • Flexible but requires management
Smart Pointers
  • unique_ptr: Exclusive ownership
  • shared_ptr: Shared ownership
  • weak_ptr: Non-owning observation
  • Automatic memory management
  • Exception safety guaranteed
Memory Safety
  • RAII pattern for resource management
  • Rule of Three/Five for classes
  • Use tools to detect leaks
  • Avoid manual memory management
  • Prefer standard library containers

Modern C++ Memory Philosophy

In modern C++ (C++11 and later), avoid raw new/delete. Use smart pointers for ownership, stack allocation for local variables, and standard containers for collections. Implement RAII for all resources. Let the compiler and standard library handle memory management whenever possible.

Next Steps

Mastering dynamic memory is crucial for advanced C++ programming. Practice with custom allocators, study memory profiling tools, explore multi-threaded memory management, and learn about memory-mapped files. Understanding memory at a deep level will make you a more effective systems programmer.