Dynamic Memory — new, delete, new[], delete[], Memory Leaks
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
- What is the difference between stack and heap allocation?
- What happens if you use
deleteinstead ofdelete[]? - Write a function that allocates an array of
ndoubles, fills them with their index, and returns the pointer. Document who is responsible for cleanup. - What is placement new and when would you use it?
- 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
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