Inevitably, our program will run, but it will not work as we intended. Some boolean we thought would be true
will be false
, or some number will be different to what we expected it to be.
When this happens, and we don't understand why, our first task will be to try to get some observability on what is happening at run time.
We can think of programming and software design as having two "phases" - build time, and run time.
Build time happens when we press the "build" button in our editor. It covers all the processes that are involved in compiling and packaging our software into a product, ready to send to users.
Run time is when when we, or our users, later run our software.
In the context of bugs and errors, an example of a build time issue is something that prevents our software from compiling at all. You may already have seen some of those - perhaps a missing semicolon, or some other misuse of syntax.
However, just because our code builds successfully, that doesn't mean it's correct. There may still be run time errors. Run time errors are situations where our software will crash, or not do what we expected whenever someone is running the program.
We already know one way to get that observability - we could scatter a lot of cout
statements throughout our code to see the state of variables through time. But there is a more efficient way: using a debugger.
Lets create a really simple program to see how a debugger can help us. There are no new concepts here - we're just creating and updating some variable. These are things we've already seen:
#include <iostream>
using namespace std;
int Health { 100 };
bool isDead { false };
int main()
{
Health -= 10;
isDead = Health <= 0;
Health -= 200;
isDead = Health <= 0;
}
Debugging involves the introduction of "breakpoints" to our code. The debugger will run our program until it reaches a breakpoint, at which point it will pause, and allow us to examine the state of our running application at that specific time.
In most editors, we set a breakpoint by clicking an area to the left of the relevant line of code. This should create some visual indicator to show where our breakpoint is.
In Visual Studio and many other editors, it appears as a red circle, as shown to the left of line 9 below.
With our breakpoint set, we can then tell our editor to start the debugger. In Visual Studio, this is the large "Local Windows Debugger" button on our top menu.
When our debugger runs, it should pause at the breakpoint we earlier set. We will often also get a lot of new UI elements and features to help us.
In Visual Studio, for example, we can now hover our pointer over any variable name to see it's current value.
In the below screenshot, Health
still has its initial value of 100
. The line of code our debugger has paused at will reduce Health
by 10
, but that line hasn't run yet.
To control the execution of our program when debugging, we should see some new UI elements. We'll discuss these more as we cover more advanced topics, but for now, the most important commands are Step Over
and Continue
In Visual Studio, these appear on our top toolbar, and are also accessible from the "Debug" menu.
Step Over will cause the debugger to execute the next line of code. In our situation, by pressing Step Over, we will execute line 9, and pause before line 10.
Executing line 9 will decrease Health
by 10
so, as expected, Health
now has a value of 90
after pressing Step Over once.
Continue will instruct the debugger to continue executing our program, until it reaches the next breakpoint. If we have no further breakpoints, it will continue to completion.
Debuggers generally will not let us move backwards. However, we can just restart from scratch if we advance too far.
With our initial exploration of variables, maths and booleans out of the way, it's time to move on to the next chapter!
The next section covers Functions and Loops, which will allow us to create much more interesting programs.
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way