DEV Community

Shankar L
Shankar L

Posted on

Memory Allocation

Why should you care?

Every program needs memory.

When you create:

int age = 20;
Enter fullscreen mode Exit fullscreen mode

or:

Person user = new Person();
Enter fullscreen mode Exit fullscreen mode

the program needs somewhere to store the required data.

But memory is not simply one giant empty space.

The runtime and operating system organize memory into different regions and manage how that memory is allocated and released.

Understanding memory allocation helps explain:

  • Stack and heap
  • Objects
  • Local variables
  • Dynamic memory
  • Memory leaks
  • Garbage collection
  • Pointers
  • References
  • Segmentation faults
  • Out-of-memory errors

The Problem

Imagine your program needs memory for different things:

Local variables
Objects
Arrays
Function calls
Global data
Temporary values
Enter fullscreen mode Exit fullscreen mode

Where should each piece of data go?

And once memory is no longer needed:

Who releases it?
When is it released?
Can it be reused?
What happens if memory runs out?
Enter fullscreen mode Exit fullscreen mode

Memory allocation is the process of answering these questions.

The Concept

At a high level, a running program has several memory regions.

A simplified model looks like:

┌────────────────────────┐
│ Code                   │
├────────────────────────┤
│ Global / Static Data   │
├────────────────────────┤
│ Heap                   │
│                        │
│        ↓               │
│                        │
├────────────────────────┤
│ Stack                  │
│        ↑               │
└────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

The exact memory layout varies by operating system, architecture, runtime, and executable format.

Two regions are especially important:

Stack
Heap
Enter fullscreen mode Exit fullscreen mode

Simple Explanation

Think of memory like a large hotel.

The hotel has rooms.

When someone needs a room:

Request room
    ↓
Find available room
    ↓
Assign room
Enter fullscreen mode Exit fullscreen mode

When they leave:

Room becomes available
    ↓
Can be assigned again
Enter fullscreen mode Exit fullscreen mode

Memory allocation works similarly.

Program needs memory
        ↓
Memory allocator finds space
        ↓
Memory is assigned
        ↓
Program uses it
        ↓
Memory is released
        ↓
Space can be reused
Enter fullscreen mode Exit fullscreen mode

The major difference is that computers have sophisticated mechanisms for managing these regions efficiently.

Real-world Analogy

Imagine a classroom.

For a short activity, the teacher gives each student a temporary seat.

When the class ends:

Students leave
    ↓
Seats become available
Enter fullscreen mode Exit fullscreen mode

This resembles short-lived stack-based execution.

Now imagine a storage room.

Items can be placed there and remain for an unpredictable amount of time.

Add item
   ↓
Keep item
   ↓
Remove item when no longer needed
Enter fullscreen mode Exit fullscreen mode

This is more similar to dynamic allocation on the heap.

The important difference is lifetime.

Stack Allocation

Consider:

static void calculate() {
    int x = 10;
    int y = 20;

    int result = x + y;
}
Enter fullscreen mode Exit fullscreen mode

The function has local execution state.

Conceptually:

calculate()
┌─────────────────┐
│ result          │
│ y               │
│ x               │
└─────────────────┘
Enter fullscreen mode Exit fullscreen mode

When the function returns, its associated call state is no longer needed.

calculate()
    ↓
return
    ↓
stack space becomes available
Enter fullscreen mode Exit fullscreen mode

This is why stack-based allocation is generally associated with structured function-call lifetimes.

The exact placement of individual variables is compiler-dependent.

Heap Allocation

Now consider:

Person user = new Person();
Enter fullscreen mode Exit fullscreen mode

The new operation creates an object dynamically.

Conceptually:

Stack
┌──────────────┐
│ user         │
└──────┬───────┘
       │
       ↓
Heap
┌──────────────┐
│ Person       │
│ name         │
│ age          │
└──────────────┘
Enter fullscreen mode Exit fullscreen mode

The variable user provides access to the object.

The object itself has a lifetime that is not simply tied to the current function's stack frame.

In Java, the garbage collector manages the object's memory.

Dynamic Memory Allocation

Languages such as C allow programmers to explicitly request memory.

For example:

int *p = malloc(sizeof(int));
Enter fullscreen mode Exit fullscreen mode

Conceptually:

p
 ↓
Heap
┌─────────────┐
│             │
│  allocated  │
│   memory    │
│             │
└─────────────┘
Enter fullscreen mode Exit fullscreen mode

The program can then use that memory:

*p = 42;
Enter fullscreen mode Exit fullscreen mode

When it is no longer needed:

free(p);
Enter fullscreen mode Exit fullscreen mode

The memory is released.

This gives the programmer much more control, but also creates more opportunities for mistakes.

Allocation and Deallocation

The general lifecycle is:

Request
   ↓
Allocate
   ↓
Use
   ↓
Release
Enter fullscreen mode Exit fullscreen mode

For manual memory management:

malloc()
   
Use memory
   
free()
Enter fullscreen mode Exit fullscreen mode

For garbage-collected languages:

Create object
   ↓
Use object
   ↓
Object becomes unreachable
   ↓
Garbage collector
   ↓
Memory reclaimed
Enter fullscreen mode Exit fullscreen mode

The mechanism differs, but the fundamental goal is the same:

Reuse memory efficiently without allowing programs to access memory they no longer own.

Memory Allocation Example

Consider this C program:

#include <stdlib.h>

int main() {

    int *numbers = malloc(5 * sizeof(int));

    if (numbers == NULL) {
        return 1;
    }

    numbers[0] = 10;
    numbers[1] = 20;
    numbers[2] = 30;
    numbers[3] = 40;
    numbers[4] = 50;

    free(numbers);

    return 0;
}
Enter fullscreen mode Exit fullscreen mode

The important sequence is:

malloc()
   ↓
Memory allocated
   ↓
Program uses memory
   ↓
free()
   ↓
Memory released
Enter fullscreen mode Exit fullscreen mode

If free() is forgotten, the allocated memory may remain unavailable to the program until the process terminates.

This is called a memory leak.

Memory Leaks

Consider:

void function() {

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

    // use p

}
Enter fullscreen mode Exit fullscreen mode

If p is lost without calling:

free(p);
Enter fullscreen mode Exit fullscreen mode

the allocated memory can become unreachable while still allocated.

Conceptually:

Pointer
   ↓
Allocated memory

Pointer disappears

Allocated memory
   ↓
No way for program to access it
Enter fullscreen mode Exit fullscreen mode

This is a memory leak.

Repeated leaks can eventually cause:

Memory usage increases
        ↓
Available memory decreases
        ↓
Allocation failures
Enter fullscreen mode Exit fullscreen mode

Dangling Pointers

The opposite problem can also happen.

Consider:

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

*p = 42;

free(p);
Enter fullscreen mode Exit fullscreen mode

After:

free(p);
Enter fullscreen mode Exit fullscreen mode

the memory is no longer valid for use through p.

But p still contains the old address.

Conceptually:

p
 ↓
Invalid memory
Enter fullscreen mode Exit fullscreen mode

Such a pointer is called a dangling pointer.

Using it can produce undefined behavior.

A common defensive practice is:

free(p);
p = NULL;
Enter fullscreen mode Exit fullscreen mode

This does not fix every ownership problem, but it prevents that particular pointer from continuing to contain the stale address.

Common Mistakes

Mistake 1: Thinking new always means stack allocation

In languages such as Java:

new Person()
Enter fullscreen mode Exit fullscreen mode

creates an object dynamically.

It should not be mentally modeled as simply "put this object on the stack."

The object and the reference to it are different concepts.

Mistake 2: Forgetting allocation failure

In C:

int *p = malloc(size);
Enter fullscreen mode Exit fullscreen mode

may fail.

Always consider:

if (p == NULL) {
    // handle failure
}
Enter fullscreen mode Exit fullscreen mode

Mistake 3: Using memory after freeing it

Bad:

free(p);

*p = 10;
Enter fullscreen mode Exit fullscreen mode

The memory is no longer valid for that use.

Mistake 4: Freeing the same memory twice

Bad:

free(p);
free(p);
Enter fullscreen mode Exit fullscreen mode

This can cause undefined behavior.

Mistake 5: Assuming garbage collection means no memory problems

Garbage-collected languages can still suffer from excessive memory usage.

For example, if a program accidentally keeps references to objects:

Reference
   ↓
Object
Enter fullscreen mode Exit fullscreen mode

the garbage collector considers those objects reachable and cannot reclaim them.

This can still lead to memory pressure or an out-of-memory condition.

Advanced Notes

Allocator

When a program requests dynamic memory, it does not necessarily receive memory directly from the operating system for every small allocation.

A memory allocator manages larger regions and divides them into usable blocks.

Conceptually:

Operating System
       ↓
Allocator obtains memory
       ↓
Allocator manages blocks
       ↓
Program requests allocation
       ↓
Allocator returns suitable block
Enter fullscreen mode Exit fullscreen mode

This reduces the overhead of constantly asking the operating system for small pieces of memory.

Fragmentation

Repeated allocation and deallocation can create fragmented free space.

Imagine:

┌────┬────┬────┬────┬────┐
│Used│Free│Used│Free│Used│
└────┴────┴────┴────┴────┘
Enter fullscreen mode Exit fullscreen mode

There is free memory, but it is split into separate regions.

This is fragmentation.

Two major forms are:

Internal fragmentation
External fragmentation
Enter fullscreen mode Exit fullscreen mode

The exact behavior depends on the allocator and allocation strategy.

Memory Allocation and Garbage Collection

In a garbage-collected language, the programmer generally does not explicitly call something like:

free(object)
Enter fullscreen mode Exit fullscreen mode

Instead, the runtime tracks object reachability.

Conceptually:

Root
 ↓
Object A
 ↓
Object B
Enter fullscreen mode Exit fullscreen mode

As long as an object is reachable, it remains potentially useful.

If:

Root
 ↓
Object A
Enter fullscreen mode Exit fullscreen mode

and Object B is no longer reachable:

Object B
   ↓
unreachable
Enter fullscreen mode Exit fullscreen mode

the garbage collector can eventually reclaim it.

This is called automatic memory management.

Allocation Is Not the Same as Initialization

These concepts are related but different.

Allocation:

Reserve memory
Enter fullscreen mode Exit fullscreen mode

Initialization:

Give that memory an initial value
Enter fullscreen mode Exit fullscreen mode

For example:

int *p = malloc(sizeof(int));
Enter fullscreen mode Exit fullscreen mode

allocates memory, but the allocated bytes are not automatically initialized to a useful integer value.

Whereas:

int *p = calloc(1, sizeof(int));
Enter fullscreen mode Exit fullscreen mode

allocates and zero-initializes the requested storage.

Understanding this distinction is important in low-level programming.

The Bigger Picture

Memory allocation connects directly to everything we have discussed so far.

Variables
   ↓
Memory Addresses
   ↓
Pointers / References
   ↓
Memory Allocation
   ↓
Stack + Heap
   ↓
Objects + Data Structures
Enter fullscreen mode Exit fullscreen mode

And at the system level:

Program
   ↓
Runtime / Allocator
   ↓
Operating System
   ↓
Virtual Memory
   ↓
Physical Memory
Enter fullscreen mode Exit fullscreen mode

The memory your program sees is usually virtual memory, not simply raw physical RAM.

The operating system maps virtual addresses to physical memory using hardware-supported mechanisms such as page tables.

The Most Important Mental Model

Do not think:

Variable = Memory
Enter fullscreen mode Exit fullscreen mode

Instead think:

Program
   ↓
Requests / uses memory
   ↓
Memory manager
   ↓
Memory region
   ↓
Data
Enter fullscreen mode Exit fullscreen mode

And remember that different data can have different lifetimes:

Short-lived function state
        ↓
Stack-oriented execution

Dynamically managed data
        ↓
Heap-oriented allocation
Enter fullscreen mode Exit fullscreen mode

The exact implementation depends on the programming language and runtime.

Summary

Memory allocation is the process of providing a program with memory for storing and working with data.

The simplified lifecycle is:

Request
   ↓
Allocate
   ↓
Use
   ↓
Release / Reclaim
Enter fullscreen mode Exit fullscreen mode

The two major memory concepts are:

Stack
→ Closely associated with active function calls

Heap
→ Used for dynamically managed data
Enter fullscreen mode Exit fullscreen mode

In manual memory management:

Allocate
   ↓
Use
   ↓
Free
Enter fullscreen mode Exit fullscreen mode

In garbage-collected systems:

Allocate
   ↓
Use
   ↓
Become unreachable
   ↓
Garbage collector reclaims
Enter fullscreen mode Exit fullscreen mode

The most important problems to understand are:

Memory Leak
Dangling Pointer
Double Free
Fragmentation
Out-of-Memory
Enter fullscreen mode Exit fullscreen mode

The deeper lesson is:

Memory allocation is not just about getting bytes. It is about managing the lifetime, ownership, accessibility, and reuse of memory.

Once you understand that, concepts like heap allocation, garbage collection, pointers, virtual memory, memory leaks, and data structures start fitting together as one system.

Top comments (0)