Skillversitas

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

  1. 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.

  2. delete ptr — Frees the memory allocated by new. Only for a single object, not for an array.

  3. int* arr = new int[5] — Allocates memory for a 5-element array. The elements are not initialized (random values).

  4. delete[] arr — Frees the array memory. You must use delete[] for a new[] allocation, not plain delete.

  5. 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.

  6. 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.

  7. unique_ptr<int[]> — A smart pointer for arrays that frees memory automatically. Replaces manual new[]/delete[].

  8. 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.