Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 6

Stack vs. Heap Memory in C++

Recommended resources

Hello! Welcome to the sixth lesson in your "C++ for ESP32" course.

Introduction

In our last lesson, we explored C++ references, establishing them as a safe and efficient way to pass objects to functions, avoiding the cost of creating copies. We saw that whether we pass an object by value, pointer, or reference, the original object must exist somewhere in memory.

Today, we'll answer the question of where that memory is. The learning outcome for this lesson is to differentiate between stack and heap memory allocation in C++. We will explore the two primary memory areas available to your program, how they work, and their distinct characteristics. Understanding this is fundamental for writing efficient and stable code, especially in memory-constrained environments like the ESP32, where every byte counts.


1. A Program's Memory Layout

When your C++ program runs, the operating system allocates a virtual memory space for it. This space is organized into several distinct segments. While the stack and heap are our focus, it's useful to see the bigger picture.

This diagram shows the typical memory layout for a program. Our focus today is on the top two sections: the Stack and the Heap.

Here's a quick breakdown:

  • Text/Code Segment: Contains your compiled program instructions (the machine code). This area is read-only.
  • Data & BSS Segments: Store global and static variables.
  • Heap: A large, flexible region of memory for dynamic allocation. It typically grows "upwards" from low to high memory addresses.
  • Stack: A fixed-size region of memory for local variables and function call management. It typically grows "downwards" from high to low memory addresses.

It's important to clarify a common point of confusion: the "stack" memory region and the "heap" memory region are not the same as the stack and heap data structures you might study in a computer science course. However, the stack memory region is managed using a stack-like (LIFO) mechanism.


2. The Stack: Fast, Automatic, and Temporary

The stack is the default memory location for most variables you declare inside a function. It's designed for speed and simplicity.

When a function is called, a "stack frame" is created and "pushed" onto the stack. This frame holds the function's parameters, local variables, and the return address. When the function finishes, its entire frame is "popped" off the stack, automatically deallocating all its local variables.

Pointers and dynamic memory - stack vs heap

This video from mycodeschool provides an excellent visual explanation of how stack frames are created and destroyed as functions are called and return.

Watch from 01:38 to 05:20. Focus on the concept of the 'call stack' and how a new stack frame is allocated for each function call and then removed when the function completes.

Key Characteristics of the Stack:

  • Automatic Management: Memory is allocated and deallocated automatically by the compiler. You don't need to manually free it. This is why it's also called automatic storage.
  • Extremely Fast: Allocating memory on the stack is as simple as moving a single pointer (the stack pointer). This is typically a single CPU instruction.
  • Scope-Based Lifetime: Variables on the stack exist only within the scope (the {...} block) where they are defined. Once the program execution leaves that scope, they are destroyed.
  • Fixed Size: The stack has a limited, pre-determined size. If you try to allocate too much memory on it (e.g., by calling too many nested functions or declaring a very large local array), you will get a stack overflow error, which will crash your program. This is a critical consideration on an ESP32, which has a much smaller stack than a desktop PC.

Here is a simple example of stack allocation:

void my_function() {
    // These variables are all allocated on the stack.
    int score = 100;
    double measurements[10];
    Led my_led(13); // Our Led object from a previous lesson
    
} // When the function ends, 'score', 'measurements', and 'my_led' are all automatically destroyed.

int main() {
    my_function();
    // The variables from my_function() no longer exist here.
    return 0;
}

3. The Heap: Flexible, Manual, and Persistent

The heap is a large pool of memory available for your program to use when the stack isn't suitable. It's also known as the free store or dynamic memory.

Unlike the stack, the heap is not managed automatically. You, the programmer, must explicitly request a block of memory and, crucially, explicitly release it when you're done.

Stack vs Heap Memory in C++

This video from The Cherno is excellent for understanding the mechanics and performance implications of using the heap. It contrasts stack and heap allocation directly.

Please watch the following segments: Allocation Syntax (02:47 - 04:05): See how the new keyword is used to allocate on the heap, contrasting it with stack allocation. Deallocation (08:45 - 09:14): Understand the need to use the delete keyword to free heap memory. Underlying Mechanism (10:29 - 12:05): Learn why heap allocation is slower—it involves function calls (malloc) and bookkeeping. Performance & When to Use (12:05 - 13:39): The key takeaway on the performance difference and the reasons for choosing the heap.

Key Characteristics of the Heap:

  • Manual Management: In C++, you allocate memory on the heap using new and must deallocate it using delete. For arrays, you use new type[size] and delete[].
  • Memory Leaks: If you allocate memory with new but forget to free it with delete, that memory becomes "lost" for the lifetime of your program. This is a memory leak and is a serious bug, especially in long-running embedded systems.
  • Slower Allocation: Allocating memory on the heap is significantly slower than on the stack. It involves a function call that searches for a suitable free block of memory and performs bookkeeping.
  • Flexible Lifetime: Memory allocated on the heap persists until it is explicitly deallocated, regardless of which scope it was created in. This allows you to create an object in one function and use it in another.
  • Large Size: The heap is generally much larger than the stack, making it suitable for large data objects (e.g., network buffers, sensor data logs).

Here's an example showing how to use the heap to create an object that outlives its creation scope:

// This function creates an array on the heap and returns a pointer to it.
int* create_data_buffer(int size) {
    // 'size' can be determined at runtime.
    // The memory is allocated on the heap.
    int* buffer = new int[size]; 
    return buffer; // The allocated memory is NOT destroyed when the function returns.
}

int main() {
    int* my_buffer = create_data_buffer(50); // my_buffer points to 50 ints on the heap.

    // We can use the buffer here.
    my_buffer[0] = 123;

    // CRITICAL: Since we allocated it, we MUST deallocate it when we are done.
    // Because it's an array, we use delete[].
    delete[] my_buffer; 

    return 0;
}

4. Stack vs. Heap: A Summary

The choice between stack and heap is a fundamental trade-off between performance, safety, and flexibility.

To solidify your understanding, please read the following summary and review the comparison table.

Stack vs Heap Memory Allocation

This article from GeeksForGeeks provides a concise summary and a clear comparison table that highlights the key differences between stack and heap allocation.

Read the sections titled 'Key Differences Between Stack and Heap Allocations' and 'Comparison Chart'. This will reinforce the concepts we've just discussed.

Here is a summary of the core differences:

Aspect Stack Heap
Allocation/Deallocation Automatic (by the compiler) Manual (by the programmer using new/delete)
Speed Very fast (single instruction) Slower (function call, searching, bookkeeping)
Size Fixed and limited Flexible and large
Lifetime Tied to the scope it was created in Persists until manually deleted
Main Risk Stack Overflow (using too much memory) Memory Leaks (forgetting to delete) & Fragmentation

The Golden Rule for ESP32 Development

Given these trade-offs, the rule of thumb is simple:

Prefer the stack.

Use the heap only when you absolutely must:

  1. When you need an object to live longer than the function that creates it.
  2. When you need to allocate a very large block of memory that won't fit on the stack.
  3. When the size of the memory is not known at compile time (like in our create_data_buffer example).

Conclusion

You now understand the fundamental differences between stack and heap memory in C++. This knowledge is not just theoretical; it directly impacts the performance, stability, and resource usage of your ESP32 applications.

Key Takeaways:

  • Stack allocation is fast, automatic, and limited. Variables on the stack have a lifetime tied to their scope.
  • Heap allocation is flexible, slower, and requires manual management with new and delete. Variables on the heap persist until you explicitly free them.
  • Forgetting to delete heap memory causes memory leaks, a critical bug in embedded systems.
  • The guiding principle is to always prefer stack allocation unless the problem specifically requires the flexibility or size of the heap.

In our next lesson, we will learn how to use the const keyword. This is a vital tool for writing safer and more readable code by preventing accidental modification of variables, whether they live on the stack or the heap.