Memory Management and the Stack

Learn about stack allocation, limitations, and transitioning to the Free Store
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated

An introduction to 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.

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:

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 sufficient 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";
  }
  string Name { "Frodo" };
};

Character* SelectCharacter() {
  Character Frodo;
  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:
1�I�^H�H�PTI�#@H�`#@H�@�* �D�f.�@�@

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.

So, the Frodo pointer within our main function is pointing at memory no longer allocated to our program.

Hopefully, our compiler will have warned us of this:

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

Returning Stack-Allocated Data by Value

Updating the previous example to return Frodo by value rather than by reference would have worked as expected:

Character SelectCharacter() {
  Character Frodo;
  return Frodo;
}

int main() {
  Character SelectedCharacter {
    SelectCharacter()
  };
  std::cout << "Getting Character Name:\n";
  std::cout << SelectedCharacter.Name << '\n';
}
Getting Character Name:
Frodo
Destroying Character

When we return something from a function, that data is moved to the stack frame that called our function. In this example, it’s the main function that receiving Frodo

When we return Frodo by value, we’re moving the Frodo object. Now, the Frodo object is not destroyed when SelectCharacter ends. It is instead moved to main, and only destroyed once main ends.

Previously, when we returned a pointer, we moved just the pointer to the main function. The Frodo object is still within the SelectCharacter stack frame. Therefore, it gets destroyed with the stack frame, and the pointer that was moved to main is no longer useful.

When an object has been deleted and a pointer that pointed to it has not been updated to reflect this, it is sometimes referred to as a dangling pointer.

Preview: The Free Store

So far, we've delved into the workings of stack memory in C++ and its limitations. In the next lesson, we’ll explore the Free Store, often referred to as 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 dive into:

  • 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

Was this lesson useful?

Next Lesson

Dynamic Memory and the Free Store

Learn about dynamic memory in C++, and how to allocate objects to it using new and delete
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated
A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access

This course includes:

  • 125 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Dynamic Memory and the Free Store

Learn about dynamic memory in C++, and how to allocate objects to it using new and delete
Abstract art representing computer programming
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved