Skip to main content

Dynamic Memory Allocation

Dynamic Memory Allocation in C++

1. Why Dynamic Memory is Like Apartment Hunting

Have you ever tried to pack for a trip before knowing exactly where you're going, how long you’ll stay, or what the weather will be like?
It’s tricky, right? You end up guessing. Maybe you overpack. Maybe you underpack. But you’re stuck — because you had to decide early, without full information.

Now imagine if programming was like that too.
When we write programs, sometimes we have to guess how much memory we’ll need — before we even run the program!
What if we guess wrong?
What if the user wants to store one item... or maybe ten thousand?

This leads to a very important question:

How can we manage memory when we don't know ahead of time how much we'll need?

To help you understand this problem, let’s think about something familiar:
Apartment hunting.

When you move to a new city for a job, you often don't know exactly how much space you'll need.
Will you live alone? Will your family visit often? Will you end up getting a pet?
There’s uncertainty.

You have two choices:

  • Option 1: Buy a house immediately.
    You pick a house size upfront. Maybe it's too big, maybe too small, but once you buy it — you're stuck.
  • Option 2: Rent an apartment.
    You start small. As your needs grow, you can rent a bigger place later.
    You pay only for what you use, and you can adjust over time.

Memory management in C++ is exactly like this.

  • If you use static memory, it's like buying a house.
    You fix the size when you write your code. No changes later.
  • If you use dynamic memory, it’s like renting an apartment.
    You decide at runtime — when you actually know how much space you need.

Now, stop and think about this:

Why would you ever want to fix your memory size early, when you could wait and decide later, with more information?

That's where dynamic memory allocation (DMA) comes into the story.

It's like having the power to rent, expand, or release your living space based on your real needs — while your program is running.

Pretty neat, right?

But wait — this raises even more important questions:

  • How do we rent memory dynamically in C++?
  • How do we return it when we’re done, so we don’t get billed forever?
  • What tools does C++ give us to handle this safely?

we’re going to explore all of that.
We'll meet two of the most important memory management tools in C++:
new and delete.

They’re like your real estate agent and your moving company:

  • new helps you rent a chunk of memory from the system.
  • delete helps you return it when you're finished, so you don't waste resources.

2. What is Dynamic Memory Allocation (DMA)? And Why Bother?

So, now we know memory can either be "bought early" (static) or "rented later" (dynamic).
But what exactly happens inside a program when we talk about memory?
And what makes dynamic memory so special?

Let’s start very simply.

Two Kinds of Memory Spaces: Stack and Heap

Imagine your computer has two main "areas" where it stores stuff during a program’s life:

  • The Stack:
    A small, super-organized suitcase you pack before your trip.
    You know exactly how many shirts, shoes, and gadgets you’ll need, so you arrange them neatly.
  • The Heap:
    A giant warehouse you can walk into whenever you want, take as much space as you need, and throw stuff into.
    No need to plan ahead — just grab space when you need it!

In C++, most of the time, when you create a simple variable like this:

int x = 5;

you're using the stack.
The system quickly finds a little spot for x on the stack.

It’s fast, automatic, and efficient.
But... it’s limited — both in size and flexibility.

What if you don't know how many things you need to store when you write your program?

What if a user tells you at runtime:

"Hey, I want to store 10,000 numbers!"
But your program only reserved space for 100?

That's where dynamic memory — memory from the heap — comes to the rescue.

In the heap, you can grab memory during the program’s execution — based on actual needs.

It’s like walking into the warehouse with a shopping cart:

  • Need one box? Grab one.
  • Need a thousand? No problem — just grab more.

Why Not Just Use the Stack for Everything?

Good question.
The stack is awesome because it’s super fast and automatic.

But it has two major problems:

  1. Limited Size:
    The stack is tiny compared to the heap.
    (On many systems, the stack might be just 1MB or 8MB!)
  2. Fixed Structure:
    Stack memory is for stuff you know ahead of time.
    It’s tightly organized — you can’t just grab and drop memory randomly.

Quick Example:

Imagine you wrote:

int arr[10000];  // a big array on the stack

If your system has a small stack, your program might crash — right there.
(No warning. Just "Segmentation Fault".)

But if you use the heap, you can request 10,000, 100,000, or even millions of elements (if your system has enough free memory).

What Does "Dynamic" Actually Mean?

"Dynamic" means changing at runtime — while the program is running.

You’re no longer stuck with decisions made while writing code.
You can respond to real-world needs:

  • User input: "User enters how many grades to store? Let’s allocate just that much."
  • Data processing: "File size unknown? Let’s allocate as we read it."
  • Games and graphics: "Number of players, enemies, objects changes? No problem."

Wait, so how exactly do we grab memory from the heap?

Good instinct!
In C++, we use special tools for that: the new and delete operators.

They’re like your personal access card to the memory warehouse.

  • new lets you rent memory.
  • delete lets you give it back.

How Stack vs Heap Variables Differ

Feature

Stack

Heap

Allocated

Automatically

Manually (new)

Size

Small, fixed

Big, flexible

Lifetime

Auto-ends with scope

You control when it ends

Speed

Super fast

Slower

Risk

Low (unless stack overflows)

High (memory leaks if mismanaged)


3. Enter new and delete — Your Keys to the Warehouse

Alright, now we understand why the heap exists and why dynamic memory is important.
But we still haven't answered a very practical question:

How exactly do we grab some memory when we need it?

And just as important:

How do we give it back when we’re done?

Imagine the heap — the big warehouse — has strict rules:

  • You can’t just walk in and grab stuff.
  • You need a special key.
  • And you need to return what you borrow, or you’ll be charged forever!

In C++, those special keys are two simple operators:

  • new → Ask for memory
  • delete → Give memory back

Let’s start exploring them one by one.

The new Operator — Renting Space on the Heap

When you want to reserve memory dynamically, you use new.

Here’s the basic idea:

int* p = new int;

Let's break this down slowly:

  • int* p → "Hey, I'm declaring a pointer named p that will point to an int."
  • new int → "Please allocate enough space in the heap for one integer."

So, after this line:

  • C++ finds space for one int in the heap.
  • new returns the address of that space.
  • p holds that address.

Important Note:

Memory allocated with new is not initialized automatically (unless you're using special versions of new).
It might contain garbage until you assign a value.

For example:

*p = 10;  // Now you're putting 10 into the allocated memory.
Why a Pointer?
Good question!
When you allocate memory dynamically, you don't know where it will be.
The memory could be anywhere in the heap!
So you need a pointer — something that remembers the exact address.

Using the Memory

After allocating:

int* p = new int;
*p = 42;
cout << *p;  // prints 42

Simple, right?
It's just like using a normal variable — but now you control the memory directly.

The delete Operator — Giving Space Back

Now here’s the most important thing:

If you take memory from the heap with new, you must give it back with delete.

Otherwise, the memory stays reserved forever — even if you don’t need it anymore.

(Imagine renting a huge storage unit and never returning the keys — you keep getting charged forever!)

To return memory:

delete p;

That’s it.

This tells C++: "Hey, I'm done with that memory. You can reclaim it."

A Complete Mini Example

#include <iostream>
using namespace std;
 
int main() {
    int* p = new int;  // allocate
    *p = 99;           // assign
    cout << *p << endl; // use
    delete p;          // free
    return 0;
}

What happens, line-by-line?

  1. new int asks for heap memory and returns its address.
  2. p stores that address.
  3. *p = 99 writes the value 99 into the allocated memory.
  4. cout << *p prints 99 to the console.
  5. delete p returns the memory to the system.

What Happens If You Forget delete?

If you forget to delete, the memory you reserved will never be returned.

Your program might still work, but:

  • It wastes memory.
  • Over time (especially in long-running programs), it can cause your app to slow down or even crash.
  • This problem is called a memory leak.

Memory Leak = Forgetting to clean up after yourself.

It’s like leaving dishes in the sink forever — eventually, you run out of clean dishes and space!

Good Habit Tip

After you delete a pointer, always set it to nullptr:

delete p;
p = nullptr;

This way, if you accidentally try to use p again, it’s easy to check:

if (p != nullptr) {
    // safe to use
}

This small habit saves a lot of debugging headaches later.


4. Dynamic Memory for Arrays — When One Cupcake Isn’t Enough

So far, we’ve been talking about grabbing space for one thing — like renting one locker in the warehouse.

But real-world problems aren’t usually about just one thing, right?

Imagine you open a bakery.
And on day one, a customer orders one cupcake.
Easy — you bake it, done.

But on day two, another customer walks in and says:

"I’d like two hundred cupcakes, please."

Uh-oh.
One cupcake-sized oven won’t cut it anymore!

You need a bigger system — something that can handle multiple cupcakes.

And guess what?

What if you need multiple integers?
What if you need an array of dynamic size?

Now we’re talking about dynamic arrays!

In C++, the heap lets you reserve as many elements as you want, at runtime.

Let’s see how.

Allocating an Array with new

Here’s the secret:
You can use new not just for a single variable, but for arrays too!

Syntax:

int* arr = new int[5];

Let’s unpack this:

  • int* arr → "I'm creating a pointer to an integer array."
  • new int[5] → "Please allocate enough space for 5 integers in the heap."

Now, arr points to the first element of this block of 5 integers.

You can now use arr just like a normal array!

Example: Dynamic Array in Action

#include <iostream>
using namespace std;
 
int main() {
    int* arr = new int[5];  // allocate array of 5 ints
 
    for (int i = 0; i < 5; i++) {
        arr[i] = i * 10;    // assign values
    }
 
    for (int i = 0; i < 5; i++) {
        cout << arr[i] << " ";  // print values
    }
 
    delete[] arr;  // free array memory
    return 0;
}

Step-by-step:

  1. new int[5] → Allocates space for 5 integers.
  2. arr[i] = i * 10 → Fills the array with multiples of 10: 0, 10, 20, 30, 40.
  3. cout loop → Prints them out.
  4. delete[] arr → Notice the brackets []!
    You must use delete[] when you allocated an array!

Super Important: delete vs delete[]

  • If you used new, you must use delete.
  • If you used new[], you must use delete[].

Wrong:

int* arr = new int[5];
delete arr;  //  wrong — mismatch!

Right:

int* arr = new int[5];
delete[] arr;  //  correct

Why?

Because C++ needs to know if it’s freeing one block or multiple blocks.
(If you use the wrong form, it might leak memory, or worse, corrupt it.)

Why Dynamic Arrays Matter

Dynamic arrays give you flexibility:

  • You can ask the user for the size:
int size;
cout << "Enter number of elements: ";
cin >> size;
 
int* arr = new int[size];
  • You can allocate big arrays without worrying about stack overflow.
  • You can resize (sort of — by allocating a new, bigger array and copying data — we’ll see this idea more later).

Static arrays (like int arr[100];) are stuck with a fixed size, decided at compile time.
Dynamic arrays adapt based on real conditions at runtime.

But wait, aren't there risks too?

Yes!
Dynamic memory always comes with responsibilities:

Problem

What Happens

Example

Forgetting delete[]

Memory leak

Huge arrays wasting system memory

Using after delete[]

Crash or garbage data

Dangling pointers

Wrong delete

Mismatch

Subtle, hard-to-find bugs

Moral:
If you allocate dynamically, you own it.
You must clean up!

Another Cool Example: Dynamic Arrays from User Input

#include <iostream>
using namespace std;
 
int main() {
    int n;
    cout << "How many numbers? ";
    cin >> n;
 
    int* numbers = new int[n];  // allocate array of size n
 
    cout << "Enter " << n << " numbers:\n";
    for (int i = 0; i < n; i++) {
        cin >> numbers[i];
    }
 
    cout << "You entered: ";
    for (int i = 0; i < n; i++) {
        cout << numbers[i] << " ";
    }
 
    delete[] numbers;  // free memory
    return 0;
}

This program asks the user for how many numbers, dynamically allocates space, fills the array, and then cleans up properly!


5. Dynamic Memory for Objects — Building Custom Cupcakes

So far, we’ve been dynamically creating simple stuff like integers and arrays.
Nice and useful.

But real C++ programs often deal with objects
things that bundle data and behavior together.
(Think: a Car object, a Student object, a Monster object.)

So what if you want to dynamically create objects instead of just plain integers?

Imagine you’re running a cupcake bakery again (yes, cupcakes are life).
Each cupcake isn’t just a lump of dough anymore.
Now it has:

  • A flavor
  • A size
  • A decoration

Each cupcake is a custom object.

And sometimes, you might not know how many cupcakes you’ll need until customers start walking in.

Dynamic objects to the rescue!

Let’s see how this magic works.

Allocating a Single Object with new

Just like we did with integers, you can dynamically create an object using new.

Suppose we have a simple class:

class Cupcake {
public:
    string flavor;
    Cupcake(string f) {
        flavor = f;
        cout << "Cupcake with flavor " << flavor << " created!" << endl;
    }
    void eat() {
        cout << "Eating a " << flavor << " cupcake!" << endl;
    }
};

Now, let’s dynamically create a cupcake:

Cupcake* myCupcake = new Cupcake("Chocolate");

Here’s what happens:

  • new Cupcake("Chocolate") → Dynamically allocates memory for a Cupcake object and calls its constructor with "Chocolate".
  • myCupcake holds the pointer to that object.

Now you can use it:

myCupcake->eat();  // using -> because myCupcake is a pointer

(Remember: for pointers to objects, we use -> instead of . to access members.)

Deleting the Object

And of course, when you’re done eating:

delete myCupcake;

This does two things:

  1. Calls the Cupcake destructor (if defined).
  2. Frees the memory.

IMPORTANT: Always delete any object you new.

Otherwise, the bakery (heap) gets filled with uneaten, forgotten cupcakes! (a.k.a., memory leaks.)

Line-by-Line Example

#include <iostream>
using namespace std;
 
class Cupcake {
public:
    string flavor;
    Cupcake(string f) {
        flavor = f;
        cout << "Cupcake with flavor " << flavor << " created!" << endl;
    }
    ~Cupcake() {
        cout << "Cupcake with flavor " << flavor << " eaten (destroyed)!" << endl;
    }
    void eat() {
        cout << "Eating a " << flavor << " cupcake!" << endl;
    }
};
 
int main() {
    Cupcake* myCupcake = new Cupcake("Vanilla");
    myCupcake->eat();
    delete myCupcake;
    return 0;
}

What happens:

  • new allocates and constructs a cupcake.
  • eat is called.
  • delete destroys the cupcake (calls the destructor).

Allocating Arrays of Objects Dynamically

Sometimes, you don’t just want one cupcake.

You want a batch!

You can create an array of objects dynamically too:

Cupcake* bakery = new Cupcake[3] { {"Strawberry"}, {"Chocolate"}, {"Lemon"} };

  • new Cupcake[3] → Allocates space for 3 cupcakes.
  • { {...}, {...}, {...} } → Initializes them with different flavors.

And you can use them just like an array:

bakery[0].eat();
bakery[1].eat();
bakery[2].eat();

No need for pointers (->) here because bakery[i] gives you a direct reference.

IMPORTANT:
When you're done, free the whole array:

delete[] bakery;

Notice the []!
Because you created multiple objects, you must delete[] them.

Otherwise, C++ would only destroy the first object and leave the rest hanging — memory leak alert!


6. malloc() and free() — The Older, C-style Memory Tools

Alright, so far, we’ve seen how C++ gives us nice, modern tools like new and delete for dynamic memory.

But now, it’s time to meet their older cousins from the C language:

  • malloc() → allocate memory
  • free() → deallocate memory

Why should we even care about malloc() and free() in C++?

Good question!

Because:

  • Some C++ programs (especially ones using old libraries) still use them.
  • Some low-level systems code prefers malloc() for maximum control.
  • It’s important to understand where new and delete came from — and how they’re different.

In short: knowing malloc() and free() makes you a deeper, stronger programmer.

Let’s get to it.

So... What is malloc() Exactly?

The name malloc stands for Memory ALLOCation.

Here’s the basic usage:

int* p = (int*) malloc(sizeof(int));

Whoa, that’s a mouthful compared to new int, right?
Let’s break it slowly:

  • sizeof(int) → "How big is an int?" (Usually 4 bytes.)
  • malloc(sizeof(int)) → "Give me enough space for one int."
  • (int*) → "Convert (cast) the returned address into an int*."

So after this line, p points to a chunk of heap memory big enough to hold one integer.

But wait — there's a twist:

malloc() does NOT call constructors!

It just grabs raw bytes.
If you’re creating objects (not just plain integers), malloc() doesn’t set them up properly — they’re just uninitialized blobs of memory.

Allocating Arrays with malloc()

Suppose you want an array of 5 integers:

int* arr = (int*) malloc(5 * sizeof(int));
  • 5 * sizeof(int) → "Reserve space for 5 integers."
  • malloc(...) → "Find that much space in the heap."
  • (int*) → "Treat the returned address like an int*."

Now you can use arr[0], arr[1], etc., just like a normal array!

Deallocating Memory with free()

When you're done with memory allocated by malloc(), you must free it:

free(p);

or

free(arr);

No casting, no fancy syntax — just plain free().

Simple, right?

Full Example: Malloc and Free in Action

#include <iostream>
#include <cstdlib>  // for malloc and free
using namespace std;
 
int main() {
    int* numbers = (int*) malloc(5 * sizeof(int));
 
    if (numbers == nullptr) {
        cout << "Memory allocation failed!" << endl;
        return 1;
    }
 
    for (int i = 0; i < 5; i++) {
        numbers[i] = i * 10;
    }
 
    for (int i = 0; i < 5; i++) {
        cout << numbers[i] << " ";
    }
    cout << endl;
 
    free(numbers);
    return 0;
}

This program:

  • Allocates memory for 5 integers.
  • Fills the array.
  • Prints the numbers.
  • Frees the memory.

Important Things to Watch Out For

Issue

What Happens

Why It Matters

Forgetting free()

Memory leak

Heap fills up over time

Using memory after free()

Undefined behavior

Could crash

Mixing new/free or malloc/delete

Disaster!

Always pair correctly!

Always:

  • new with delete
  • new[] with delete[]
  • malloc with free

Real Talk: Why Prefer new/delete in C++?

Here’s the key difference:

Feature

new/delete

malloc/free

Constructor/destructor calls

Yes

No

Type safety

Yes (no casting needed)

No (must cast manually)

Syntax

Cleaner

Verbose

C++-style objects

Fully supported

Nope

In modern C++:
Use new and delete whenever possible.

When to use malloc and free anyway?

  • When interfacing with old C libraries.
  • When working super close to the system.
  • When writing ultra-low-level code.

Otherwise, stick with new/delete — they are smarter, safer, and object-friendly.

Cool Analogy: Renting Lockers

  • new/delete is like a smart hotel where they prepare the room for you, and clean it up when you leave.
  • malloc/free is like a warehouse where they just hand you a raw empty locker — no setup, no cleanup.

Which feels more modern and comfortable?
Yep — C++ prefers the hotel experience!

Final Big Picture: How It All Ties Together

Let’s zoom out:

Scenario

Best Tool

Dynamic single variable

new + delete

Dynamic array

new[] + delete[]

C-style memory block

malloc + free

Complex C++ objects

new + delete (for constructor/destructor)

Dynamic memory gives you freedom and power
but it also demands responsibility.

Always clean up what you allocate.
Always match your new/delete or malloc/free.
Think about whether you’re working with raw memory or real C++ objects.