Memory Management and the Stack

Learn about stack allocation, limitations, and transitioning to the Free Store

Ryan McCombe
Updated

In this chapter, we'll introduce memory management within C++, starting with stack-allocated memory. In the beginner course, we introduced the concept of the call stack, which is generated by the function calls in our program.

The Call Stack and Debugging Functions

An introduction to how our function calls create a call stack, and how we can navigate it in a debugger.

We can see the call stack in action using a class with a constructor and destructor:

#include <iostream>

class Character {
 public:
  Character(){
    std::cout << "Creating Character\n";
  }
  ~Character(){
    std::cout << "Destroying Character\n";
  }
};

void SelectCharacter() {
  // A new stack frame is created for
  // SelectCharacter.  Local variable Frodo is
  // allocated on the stack
  Character Frodo;
  // When SelectCharacter ends, Frodo is
  // deallocated. Destructor is called as the
  // stack frame is removed
}

int main() {
  std::cout << "Program Starting\n";

  // Call SelectCharacter, creating and
  // destroying Frodo
  SelectCharacter();
  // After SelectCharacter returns, its stack 
  // frame is removed. Memory used by Frodo is freed
  std::cout << "Program Ending\n";
}
Program Starting
Creating Character
Destroying Character
Program Ending

If the concept of constructors and destructors are unfamiliar, I'd recommend reviewing this lesson:

Constructors and Destructors

Learn about special functions we can add to our classes, control how our objects get created and destroyed.

We're already familiar that we can create objects within the local scope of our functions. An example of this is the Frodo object, created within the SelectCharacter() function of our previous example.

Stack Allocated Memory

Given we can create objects in our functions, we may have predicted, therefore, that the stack has memory available to store these objects.

This is indeed the case. When we create variables in our functions, we are given the appropriate amount of memory from the stack to store those variables. When the function ends, the stack frame is removed, local variables are deleted, and the memory is freed up for other uses.

However, while the stack is incredibly efficient for managing memory on a function-by-function basis, it is not without its limitations.

Stack Limitation 1: Space

Typically, the size of the stack in a C++ program is determined by the operating system and the settings of the compiler used. For instance, on many systems, the default stack size might be around 1 MB to 2 MB.

This is generally sufficient for most routine operations and function calls. However, it's often not enough to store large objects, or large collections of objects.

Attempting to allocate large data structures on the stack can lead to a stack overflow, where the stack's limit is exceeded, potentially causing the program to crash or behave unpredictably.

Stack Limitation 2: Flexibility

This form of memory management where objects are deleted automatically is often useful, but it does restrict our options

For example, consider a scenario where we want our SelectCharacter() function to return a pointer to the character:

#include <iostream>

class Character {
public:
  ~Character() {
    std::cout << "Destroying Character\n";
  }
  std::string Name { "Frodo" };
};

Character* SelectCharacter() {
  Character Frodo; // Stack allocated
  
  // Returning a pointer to stack-allocated data
  return &Frodo;
}

int main() {
  Character* SelectedCharacter {
    SelectCharacter()
  };
  std::cout << "Getting Character Name:\n";
  std::cout << SelectedCharacter->Name;
}

The output of this program could be something like the following:

Destroying Character
Getting Character Name:
1I^HHPTI#@H`#@H@* Df.@@

The fact that line 3 was garbage is perhaps predictable given the proceeding output: the Character has already been destroyed by the time we come to log its name. This is because it was allocated within the SelectCharacter() function's stack frame.

Once SelectCharacter() ends, that stack frame is deleted. Therefore, the Frodo pointer within our main function is pointing at memory that is no longer allocated to our program. Most compilers can detect this, and will issue a warning:

warning: reference to stack memory associated with local variable 'Frodo' returned

Preview: The Free Store

So far, we've explored the workings of stack memory and its limitations. In the next lesson, we'll explore the Free Store, sometimes also called the Heap.

What is the Free Store?

The Free Store is a region of memory that programs use to allocate objects whose lifetime is not tied to the scope of a function. Unlike stack memory, where objects are automatically managed and limited in size, the Free Store allows for dynamic memory allocation.

This means we can allocate memory at runtime, and it's your responsibility to free it when it's no longer needed.

Why Learn About the Free Store?

Understanding the Free Store is crucial because:

  1. Flexibility in Memory Allocation: It provides a more flexible way to manage memory, especially when dealing with large data or when the size of data is not known at compile time.
  2. Control Over Object Lifetimes: Objects in the Free Store remain alive until they are explicitly destroyed, giving us more control over their lifetimes.
  3. Essential for Advanced C++ Features: It lays the foundation for understanding more advanced C++ concepts like smart pointers and dynamic data structures.

What We'll Learn

In our upcoming lesson, we'll learn:

  • How to allocate and deallocate memory on the Free Store
  • Best practices for managing dynamic memory
  • Common pitfalls and how to avoid them
  • Real-world examples to solidify our understanding
Next Lesson
Lesson 12 of 128

Dynamic Memory and the Free Store

Learn about dynamic memory in C++, and how to allocate objects to it using new and delete

Questions & Answers

Answers are generated by AI models and may not have been reviewed. Be mindful when running any code on your device.

Stack vs Heap Memory
What are the key differences between stack and heap memory in C++?
Stack Overflow
What causes a stack overflow error, and how can it be prevented?
Dangling Pointers
What is a dangling pointer, and how can it be avoided?
Dynamic Memory Allocation
How do you dynamically allocate memory in C++, and why is it useful?
Stack Frame
What is a stack frame, and how is it related to function calls in C++?
Memory Leaks
What is a memory leak, and how can it be prevented in C++?
Or Ask your Own Question
Get an immediate answer to your specific question using our AI assistant