cpp

dynamic memory

01_dynamic_memory.cpp⚙️
/**
 * ============================================================
 * C++ DYNAMIC MEMORY ALLOCATION
 * ============================================================
 * 
 * This file covers:
 * - Stack vs Heap memory
 * - new and delete operators
 * - Dynamic arrays
 * - Memory leaks
 * - Common pitfalls
 * 
 * Compile: g++ -std=c++17 -Wall 01_dynamic_memory.cpp -o dynamic_memory
 * Run: ./dynamic_memory
 * 
 * ============================================================
 */

#include <iostream>
#include <cstring>

using namespace std;

// Simple class for demonstration
class Demo {
public:
    int value;
    
    Demo() : value(0) {
        cout << "    Demo default constructor" << endl;
    }
    
    Demo(int v) : value(v) {
        cout << "    Demo constructor with value " << v << endl;
    }
    
    ~Demo() {
        cout << "    Demo destructor, value was " << value << endl;
    }
};

int main() {
    cout << "============================================" << endl;
    cout << "     C++ DYNAMIC MEMORY ALLOCATION" << endl;
    cout << "============================================" << endl << endl;

    // ========================================================
    // PART 1: STACK VS HEAP
    // ========================================================
    
    cout << "--- PART 1: STACK VS HEAP ---" << endl << endl;
    
    /*
     * STACK MEMORY:
     * - Automatic allocation/deallocation
     * - Fast access
     * - Limited size
     * - Variables destroyed when scope ends
     * 
     * HEAP MEMORY:
     * - Manual allocation with new
     * - Manual deallocation with delete
     * - Larger available space
     * - Persists until explicitly freed
     */
    
    // Stack allocation
    int stackVar = 10;  // On stack, automatic cleanup
    
    // Heap allocation
    int* heapVar = new int(20);  // On heap, manual cleanup needed
    
    cout << "Stack variable: " << stackVar << endl;
    cout << "Heap variable: " << *heapVar << endl;
    
    delete heapVar;  // Must free heap memory!
    heapVar = nullptr;  // Good practice: set to nullptr after delete
    
    cout << "\nStack vs Heap Comparison:" << endl;
    cout << "┌─────────────────┬─────────────────┬─────────────────┐" << endl;
    cout << "│ Feature         │ Stack           │ Heap            │" << endl;
    cout << "├─────────────────┼─────────────────┼─────────────────┤" << endl;
    cout << "│ Allocation      │ Automatic       │ Manual (new)    │" << endl;
    cout << "│ Deallocation    │ Automatic       │ Manual (delete) │" << endl;
    cout << "│ Speed           │ Fast            │ Slower          │" << endl;
    cout << "│ Size            │ Limited (~MB)   │ Large (~GB)     │" << endl;
    cout << "│ Fragmentation   │ No              │ Possible        │" << endl;
    cout << "│ Scope           │ Function/block  │ Until deleted   │" << endl;
    cout << "└─────────────────┴─────────────────┴─────────────────┘" << endl;
    
    cout << endl;

    // ========================================================
    // PART 2: NEW AND DELETE FOR SINGLE OBJECTS
    // ========================================================
    
    cout << "--- PART 2: NEW AND DELETE ---" << endl << endl;
    
    // Allocate single int
    int* pInt = new int;      // Uninitialized
    *pInt = 42;
    cout << "new int: " << *pInt << endl;
    delete pInt;
    pInt = nullptr;
    
    // Allocate with initialization
    int* pInitInt = new int(100);  // Direct initialization
    cout << "new int(100): " << *pInitInt << endl;
    delete pInitInt;
    pInitInt = nullptr;
    
    // Allocate with braces (C++11)
    int* pBraceInt = new int{200};  // Brace initialization
    cout << "new int{200}: " << *pBraceInt << endl;
    delete pBraceInt;
    
    // Allocate object
    cout << "\nAllocating Demo object:" << endl;
    Demo* pDemo = new Demo(42);
    cout << "pDemo->value = " << pDemo->value << endl;
    delete pDemo;  // Calls destructor
    
    cout << endl;

    // ========================================================
    // PART 3: DYNAMIC ARRAYS
    // ========================================================
    
    cout << "--- PART 3: DYNAMIC ARRAYS ---" << endl << endl;
    
    // Allocate array of 5 integers
    int* arr = new int[5];
    
    // Initialize array
    for (int i = 0; i < 5; i++) {
        arr[i] = (i + 1) * 10;
    }
    
    // Access array
    cout << "Dynamic array: ";
    for (int i = 0; i < 5; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
    
    // IMPORTANT: Use delete[] for arrays!
    delete[] arr;  // Not just delete!
    arr = nullptr;
    
    // Array with initialization (C++11)
    int* arr2 = new int[5]{1, 2, 3, 4, 5};
    cout << "Initialized array: ";
    for (int i = 0; i < 5; i++) {
        cout << arr2[i] << " ";
    }
    cout << endl;
    delete[] arr2;
    
    // Array of objects
    cout << "\nAllocating array of 3 Demo objects:" << endl;
    Demo* demos = new Demo[3];  // Calls default constructor 3 times
    cout << "Deleting array of Demo objects:" << endl;
    delete[] demos;  // Calls destructor 3 times
    
    cout << endl;

    // ========================================================
    // PART 4: 2D DYNAMIC ARRAYS
    // ========================================================
    
    cout << "--- PART 4: 2D DYNAMIC ARRAYS ---" << endl << endl;
    
    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];
    }
    
    // Initialize
    int count = 1;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = count++;
        }
    }
    
    // Display
    cout << "2D Dynamic Array (" << 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 (reverse order!)
    for (int i = 0; i < rows; i++) {
        delete[] matrix[i];  // Delete each row first
    }
    delete[] matrix;  // Then delete the array of pointers
    
    cout << "\n💡 For 2D arrays, consider using vector<vector<int>>" << endl;
    
    cout << endl;

    // ========================================================
    // PART 5: MEMORY LEAKS
    // ========================================================
    
    cout << "--- PART 5: MEMORY LEAKS ---" << endl << endl;
    
    cout << "Common causes of memory leaks:" << endl;
    cout << "─────────────────────────────────────────" << endl;
    
    cout << "\n1. Forgetting to delete:" << endl;
    cout << "   int* p = new int;  // Allocated" << endl;
    cout << "   // forgot delete p; → LEAK!" << endl;
    
    cout << "\n2. Overwriting pointer:" << endl;
    cout << "   int* p = new int(10);" << endl;
    cout << "   p = new int(20);  // Lost reference to first int!" << endl;
    
    cout << "\n3. Returning without cleanup:" << endl;
    cout << "   int* p = new int;" << endl;
    cout << "   if (error) return;  // Forgot to delete!" << endl;
    
    cout << "\n4. Exception thrown before delete:" << endl;
    cout << "   int* p = new int;" << endl;
    cout << "   mayThrow();  // If exception, p not deleted!" << endl;
    cout << "   delete p;" << endl;
    
    cout << "\n5. Using delete instead of delete[]:" << endl;
    cout << "   int* arr = new int[100];" << endl;
    cout << "   delete arr;  // WRONG! Use delete[] arr;" << endl;
    
    cout << endl;

    // ========================================================
    // PART 6: COMMON PITFALLS
    // ========================================================
    
    cout << "--- PART 6: COMMON PITFALLS ---" << endl << endl;
    
    // Double delete
    cout << "1. Double Delete (DANGEROUS!):" << endl;
    cout << "   int* p = new int;" << endl;
    cout << "   delete p;" << endl;
    cout << "   delete p;  // UNDEFINED BEHAVIOR!" << endl;
    cout << "   Solution: p = nullptr after delete" << endl;
    
    // Dangling pointer
    cout << "\n2. Dangling Pointer:" << endl;
    cout << "   int* p = new int(10);" << endl;
    cout << "   delete p;" << endl;
    cout << "   *p = 20;  // UNDEFINED BEHAVIOR!" << endl;
    cout << "   Solution: Set p = nullptr after delete" << endl;
    
    // Using uninitialized pointer
    cout << "\n3. Using Uninitialized Pointer:" << endl;
    cout << "   int* p;  // Not initialized!" << endl;
    cout << "   *p = 10;  // UNDEFINED BEHAVIOR!" << endl;
    cout << "   Solution: int* p = nullptr;" << endl;
    
    // Array bounds
    cout << "\n4. Array Out of Bounds:" << endl;
    cout << "   int* arr = new int[5];" << endl;
    cout << "   arr[10] = 100;  // UNDEFINED BEHAVIOR!" << endl;
    cout << "   Solution: Always track array size" << endl;
    
    cout << endl;

    // ========================================================
    // PART 7: NOTHROW NEW
    // ========================================================
    
    cout << "--- PART 7: ALLOCATION FAILURE ---" << endl << endl;
    
    // Regular new throws std::bad_alloc on failure
    try {
        // This would fail with very large size
        // int* huge = new int[999999999999];
        cout << "Regular new throws std::bad_alloc on failure" << endl;
    } catch (bad_alloc& e) {
        cout << "Allocation failed: " << e.what() << endl;
    }
    
    // nothrow new returns nullptr on failure
    int* safePtr = new(nothrow) int[100];
    if (safePtr == nullptr) {
        cout << "Allocation failed (nothrow)" << endl;
    } else {
        cout << "nothrow new returns nullptr on failure" << endl;
        delete[] safePtr;
    }
    
    cout << endl;

    // ========================================================
    // PART 8: DYNAMIC MEMORY BEST PRACTICES
    // ========================================================
    
    cout << "--- PART 8: BEST PRACTICES ---" << endl << endl;
    
    cout << "✓ Prefer smart pointers over raw pointers" << endl;
    cout << "✓ Use std::vector instead of dynamic arrays" << endl;
    cout << "✓ Set pointers to nullptr after delete" << endl;
    cout << "✓ Match new with delete, new[] with delete[]" << endl;
    cout << "✓ Use RAII (Resource Acquisition Is Initialization)" << endl;
    cout << "✓ Check for nullptr before using pointers" << endl;
    cout << "✓ Document ownership of dynamic memory" << endl;
    cout << "✓ Use tools like Valgrind to detect leaks" << endl;
    
    cout << "\n⚠️ Modern C++ Recommendation:" << endl;
    cout << "   Use std::unique_ptr and std::shared_ptr" << endl;
    cout << "   (covered in Smart Pointers section)" << endl;
    
    cout << endl;

    cout << "============================================" << endl;
    cout << "DYNAMIC MEMORY SUMMARY:" << endl;
    cout << "============================================" << endl;
    cout << "• new: allocates on heap" << endl;
    cout << "• delete: frees single object" << endl;
    cout << "• delete[]: frees array" << endl;
    cout << "• Always match new with delete" << endl;
    cout << "• Set nullptr after delete" << endl;
    cout << "• Prefer smart pointers!" << endl;
    cout << "============================================" << endl;

    return 0;
}

// ============================================================
// EXERCISES:
// ============================================================
/*
 * 1. Create a function that dynamically allocates an array,
 *    fills it with random numbers, and returns the pointer.
 *    Remember to document who is responsible for deletion!
 * 
 * 2. Create a dynamic string class that:
 *    - Uses new[] to allocate char array
 *    - Implements destructor to free memory
 *    - Implements append() function that reallocates
 * 
 * 3. Create a 2D matrix class with dynamic allocation:
 *    - Constructor takes rows and cols
 *    - Implements operator()(row, col) for access
 *    - Properly frees memory in destructor
 * 
 * 4. Implement a simple memory pool allocator
 * 
 * 5. Use Valgrind (if available) to check for memory leaks
 *    in a program with intentional leaks
 */
Dynamic Memory - C++ Tutorial | DeepML