Skip to content

Dynamic Memory — new, delete, new[], delete[], Memory Leaks

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about Dynamic Memory. We cover key concepts, practical examples, and best practices to help you master this topic.

C++ provides new and delete operators for dynamic memory allocation on the heap, with array forms new[] and delete[], and improper resource management causes memory leaks and undefined behavior.

What You'll Learn

You will allocate single objects and arrays on the heap using new and new[], deallocate with delete and delete[], understand the difference between stack and heap memory, detect and prevent memory leaks, handle out-of-memory conditions with exceptions and nothrow, and use placement new for constructing objects at specific memory locations.

Why It Matters

Heap allocation is essential when the size or lifetime of data cannot be determined at compile time. Every std::vector, std::string, and std::map uses dynamic memory internally. Manual heap management with raw new and delete is error-prone, but understanding it is critical for debugging, writing custom allocators, and working with embedded or real-time systems where the heap must be managed explicitly.

Learning Path

graph LR
    A["22: References"] --> B["23: Dynamic Memory"]
    B --> C["24: Smart Pointers"]
    C --> D["25: Custom Deleters"]
    style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style D fill:#4a90d9,stroke:#2c5f8a,color:#fff

Stack vs Heap

#include <iostream>

class LargeObject {
public:
    int data[10000];
    LargeObject() { std::cout << "Constructed\n"; }
    ~LargeObject() { std::cout << "Destroyed\n"; }
};

int main() {
    // Stack: limited size (~1-8 MB), fast, automatic cleanup
    int stackVar = 42;
    LargeObject stackObj;  // 40,000 bytes on stack
    
    // Heap: large (system memory), slower, manual cleanup
    LargeObject* heapObj = new LargeObject();
    delete heapObj;  // must delete explicitly
    
    // Stack memory is freed when scope exits
    // Heap memory persists until deleted
}
Property Stack Heap
Size Small (MB) Large (GB)
Speed Fast Slower
Allocation Automatic Manual (new/delete)
Lifetime Scope-bound Until deleted
Fragmentation None Possible

new and delete for Single Objects

#include <iostream>

int main() {
    // Allocate a single int on the heap
    int* p = new int(42);
    std::cout << *p << "\n";  // 42
    
    // Allocate with default initialization
    int* q = new int();   // zero-initialized
    std::cout << *q << "\n";  // 0
    
    // Free the memory
    delete p;
    delete q;
    
    // Always set to nullptr after delete
    p = nullptr;
    q = nullptr;
}

new[] and delete[] for Arrays

#include <iostream>

int main() {
    size_t size = 10;
    
    // Allocate array on heap
    int* arr = new int[size];
    
    // Elements are default-initialized (uninitialized for int)
    // Use parentheses to value-initialize:
    int* zeros = new int[size]();
    
    for (size_t i = 0; i < size; ++i) {
        arr[i] = static_cast<int>(i * 2);
    }
    
    for (size_t i = 0; i < size; ++i) {
        std::cout << arr[i] << " ";
    }
    std::cout << "\n";
    
    // Must use delete[] for arrays
    delete[] arr;
    delete[] zeros;
}

Critical: Use delete for single objects and delete[] for arrays. Using the wrong form is undefined behavior.

Memory Leaks

#include <iostream>

void leakExample() {
    int* p = new int(42);
    // No delete — memory leak
}

int main() {
    for (int i = 0; i < 1000000; ++i) {
        leakExample();
    }
    // Program consumes increasing memory
    // Memory is released only when the program exits
}

A memory leak occurs when dynamically allocated memory is never deallocated. The memory stays allocated until the program ends. In long-running programs (servers, games), leaks accumulate and eventually exhaust available memory.

Common Leak Patterns

// Pattern 1: Overwriting pointer without deleting
void leak1() {
    int* p = new int(10);
    p = new int(20);  // 10 is leaked
    delete p;
}

// Pattern 2: Exception before delete
void leak2() {
    int* p = new int(42);
    if (someCondition()) {
        throw std::runtime_error("error");  // p is leaked
    }
    delete p;
}

// Pattern 3: Forgetting delete[] for arrays
void leak3() {
    int* arr = new int[100];
    // delete arr;  // wrong form
    delete[] arr;  // correct
}

Exception Safety with Dynamic Memory

#include <iostream>
#include <memory>

// Safe approach: RAII via smart pointers
void safeFunction() {
    auto p = std::make_unique<int>(42);
    // If exception occurs, unique_ptr destructor cleans up
    doSomething(*p);
}  // automatic cleanup

Out-of-Memory Handling

#include <iostream>
#include <new>  // for std::bad_alloc, std::nothrow

int main() {
    // Default: throws std::bad_alloc on failure
    try {
        int* big = new int[1000000000];
        delete[] big;
    } catch (const std::bad_alloc& e) {
        std::cout << "Allocation failed: " << e.what() << "\n";
    }
    
    // nothrow version: returns nullptr on failure
    int* safe = new (std::nothrow) int[1000000000];
    if (!safe) {
        std::cout << "Allocation failed (nothrow)\n";
    } else {
        delete[] safe;
    }
}

Placement New

Placement new constructs an object at a pre-allocated memory address without allocating new memory.

#include <iostream>
#include <new>

struct Point {
    int x, y;
    Point(int a, int b) : x(a), y(b) {
        std::cout << "Point constructed\n";
    }
    ~Point() {
        std::cout << "Point destroyed\n";
    }
};

int main() {
    // Stack buffer large enough for a Point
    alignas(Point) char buffer[sizeof(Point)];
    
    // Construct Point at buffer address
    Point* p = new (buffer) Point(3, 4);
    std::cout << p->x << ", " << p->y << "\n";
    
    // Must call destructor explicitly for placement new
    p->~Point();
}

Placement new is used in custom allocators, Embedded Systems, and the standard library (e.g., std::vector uses it to construct elements in pre-allocated storage).

Detecting Memory Leaks

Valgrind (Linux)

g++ -g -std=c++17 leak.cpp -o leak
valgrind --leak-check=full ./leak

AddressSanitizer (GCC/Clang)

g++ -g -fsanitize=address -std=c++17 leak.cpp -o leak
./leak

Visual Studio

Use the C Runtime Library debug heap:

#define _CRTDBG_MAP_ALLOC
#include <cstdlib>
#include <crtdbg.h>
_CrtDumpMemoryLeaks();

Common Mistakes

Mistake 1: Using delete Instead of delete[]

int* arr = new int[100];
delete arr;  // undefined behavior

Mistake 2: Double Delete

int* p = new int(5);
delete p;
delete p;  // undefined behavior: double free

Set to nullptr after delete: deleting nullptr is safe.

Mistake 3: Dangling Pointer

int* p = new int(5);
delete p;
*p = 10;  // undefined behavior: use after free

Mistake 4: Deleting Memory Not Allocated by new

int x = 5;
delete &x;  // undefined behavior: x was not allocated with new

Mistake 5: Exception Before Delete

int* p = new int(5);
riskyFunction();  // may throw
delete p;  // never reached if exception

Use RAII (smart pointers) to make cleanup automatic.

Mistake 6: Mixing malloc/free with new/delete

int* p = (int*)malloc(sizeof(int));
delete p;  // undefined behavior

malloc/free and new/delete use different allocation mechanisms.

Practice Questions

  1. What is the difference between stack and heap allocation?
  2. What happens if you use delete instead of delete[]?
  3. Write a function that allocates an array of n doubles, fills them with their index, and returns the pointer. Document who is responsible for cleanup.
  4. What is placement new and when would you use it?
  5. How do you detect memory leaks in your program?

Challenge

Implement a simple Matrix class that allocates a 2D array on the heap using a single contiguous allocation (not pointer-to-pointer). Include a destructor, copy constructor, and copy assignment operator. Add element access with operator().

FAQ

What happens when `new` fails?

By default, new throws std::bad_alloc. With std::nothrow, it returns nullptr. On most modern systems with overcommit, new rarely fails in practice but can when memory is exhausted.

Is `malloc` still usable in C++?

Yes, but avoid it. malloc does not call constructors. Use new for objects and new[] for arrays. For raw memory, use ::operator new.

What is the difference between `new` and `operator new`?

new is an operator that calls operator new (a function) to allocate memory, then calls the constructor. operator new just allocates memory.

Can I overload `new` and `delete` for a class?

Yes. Class-specific operator new and operator delete allow custom allocation strategies (e.g., pool allocation for small objects).

What is the 'placement delete'?

There is no placement delete. If a placement new throws during construction, the compiler automatically calls the matching placement delete to deallocate the memory.

Should I use `new` and `delete` in modern C++?

Generally no. Prefer std::make_unique and std::make_shared. Use new/delete only when implementing low-level data structures or custom allocators.

Mini Project

Write a custom memory tracker that logs allocations:

#include <iostream>
#include <map>
#include <mutex>

class MemoryTracker {
private:
    std::map<void*, size_t> allocations_;
    size_t totalAllocated_ = 0;
    size_t totalFreed_ = 0;
    std::mutex mtx_;
    
public:
    void* allocate(size_t size) {
        void* ptr = std::malloc(size);
        if (ptr) {
            std::lock_guard<std::mutex> lock(mtx_);
            allocations_[ptr] = size;
            totalAllocated_ += size;
        }
        return ptr;
    }
    
    void deallocate(void* ptr) {
        if (ptr) {
            std::lock_guard<std::mutex> lock(mtx_);
            auto it = allocations_.find(ptr);
            if (it != allocations_.end()) {
                totalFreed_ += it->second;
                allocations_.erase(it);
            }
            std::free(ptr);
        }
    }
    
    void report() const {
        std::cout << "Allocated: " << totalAllocated_ << " bytes\n";
        std::cout << "Freed: " << totalFreed_ << " bytes\n";
        std::cout << "Current: " << (totalAllocated_ - totalFreed_) << " bytes\n";
        std::cout << "Live allocations: " << allocations_.size() << "\n";
    }
};

MemoryTracker g_tracker;

void* operator new(size_t size) {
    return g_tracker.allocate(size);
}

void operator delete(void* ptr) noexcept {
    g_tracker.deallocate(ptr);
}

int main() {
    int* p = new int(42);
    int* arr = new int[10];
    delete p;
    delete[] arr;
    g_tracker.report();
}

What's Next

Raw new and delete are error-prone. The next lesson introduces smart pointers: unique_ptr, shared_ptr, weak_ptr, and the Factory functions make_unique and make_shared.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro