The C++ 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.
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

3D art showing a ladybug
Ryan McCombe
Ryan McCombe
Posted

Let's create the following simple program to introduce the concept of the call stack.

#include <iostream>
using namespace std;

int Health { 150 };

void TakeDamage() {
  cout << " - TakeDamage function starting" << endl;
  Health -= 50;
  cout << " - TakeDamage function complete" << endl;
}

int main() {
  cout << "Main function starting" << endl;
  TakeDamage();
  cout << "We are back in main" << endl;
  TakeDamage();
  cout << "Main function complete" << endl;
}

The result of running this program can be seen here:

Main function starting
 - TakeDamage function starting
 - TakeDamage function complete
We are back in main
 - TakeDamage function starting
 - TakeDamage function complete
Main function complete

The following diagram illustrates the flow of this program through time.

A diagram showing the call stack over time

When this program runs, we can imagine that its execution has "height".

The main function is at the bottom - on the surface. It is the function that gets called first in our program. When main ends, our program ends.

main calls the TakeDamage function. In a sense, when a function calls another function, the calling function is paused, and control shifts to the function that it called.

In this example, we can imagine main as being paused until TakeDamage completes.

We could visualise this as a stack of blocks, sitting on top of each other, where each block is created by a call to a function. The function at the top of that stack is the one that is currently being executed.

If that function calls another function, it gets paused, a new block for the new function call is added to the top of the stack, and that new block is now being run.

When a function reaches the end of its execution, its block gets removed from the stack, and the block below it picks up where it was previously paused.

When the block at the very bottom of our stack - the one created by the main function gets popped off, our stack is empty, and our program quits.

This concept of function calls creating a stack is called a call stack, and the blocks are called stack frames.

A snapshot of the current state of the call stack it is shown to us when we are debugging our code:

A screenshot of the call stack from the Visual Studio debugger

Here, we see external code (ie, operating system) at the bottom of the stack. The operating system called our main function, so its stack frame is on top of the external code. And main called TakeDamage, so its stack frame is on top of main.

Lets add a break point to our code, and explore how we can use the debugger with functions.

Step Over, Step Into, Step Out

When debugging our earlier code, we had only one level of depth - everything was in the main function. There, we saw how we could advance execution line by line in the debugger using Step Over

Likely, you noticed that many different options appear when we start debugging. Many of those are to allow us to navigate the call stack created by our functions.

The four we want to talk about here are the different ways we can progress our application when debugging - Step Over, Step Into, Step Out, and Continue.

A screenshot of the visual studio Step Over button

Step Over

Were we to advance execution using Step Over, we are advancing within the current stack frame. If the line we're debugging happens to call another function, that function will create its own stack frame.

But, with step over, we don't debug that frame - we just let it complete entirely. So, Step Over runs as much code as needed until control shifts back to the frame we're currently in. From there, we can continue debugging.

Diagram illustrating the effect of step over

In our example, if we're in the main function, and Step Over a line that calls TakeDamage, the call to TakeDamage will complete entirely. Once control shifts back to main, our debugger will pause execution again, and continue to let us step through main, line by line.

Step In / Step Into

When we're on a line of code that is about to call a function, we have the option to Step Into that function call. In our case, rather than executing the TakeDamage function completely with Step Over, we could instead use Step Into.

This will add that new function's stack frame to the call stack, and the debugger would immediately take us into that new stack frame, pausing at the first line.

Diagram illustrating the effect of step in

We could then move through the TakeDamage function, line by line.

Step Out

Step Out completes execution of the current stack frame. Once it is done, it will get removed from the call stack. Our debugger will then pause at the previous stack frame - the one that called the function we were previously in.

Diagram illustrating the effect of step out

For example, at any point when debugging TakeDamage, we could Step Out, which would complete execution of the function, remove it from the call stack. Our debugger would then be paused in the main function, right after the line that called TakeDamage to generate the stack frame we were previously debugging.

Continue

Finally, we have the option to Continue. This will proceed execution until the next breakpoint we have defined. If we have no further breakpoints, we will Continue to the end of our program.

Take some time to add a break point to your code, and experiment with the debugger and how these options navigate around your code.

Establishing an understanding of the call stack concept in this simple program will greatly help understand what is going on in bigger applications, and will be vital to help us debug them when things inevitably go wrong.

Test your Knowledge

When debugging, which action lets us enter a function that is about to be called?

Up next, we'll see how we can use booleans to make our functions more powerful and dynamic

Was this lesson useful?

Ryan McCombe
Ryan McCombe
Posted
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, unlimited access!

This course includes:

  • 66 Lessons
  • Over 200 Quiz Questions
  • Capstone Project
  • Regularly Updated
  • Help and FAQ
Next Lesson

Conditional Logic in C++

Use booleans and if statements to make our functions and programs more dynamic, choosing different execution paths
3D art showing a branching path in a forest
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved