In the previous lesson on stepping and breakpoints, you learned how to use the debugger to watch the path of execution through your program. However, stepping through a program is only half of what makes the debugger useful. The debugger also lets you examine the value of variables as you step through your code.
Note: If you are not familiar with the concept of stepping, please review the previous lesson first, as this lesson builds on top of it. Our examples here will be using the Visual Studio 2005 Express debugger — if you are using a different IDE/debugger, the commands may have slightly different names or be located in slightly different locations.
Watching variables
Watching a variable is the process of inspecting the value of a variable while the program is executing in debug mode. Most debuggers provide several ways to do this. Let’s take a look at a sample program:
06 | const int nInterations = 10; |
08 | for ( int nValue=1; nValue<nInterations; nValue++) |
09 | cout << nValue << " " ; |
This is a pretty straightforward sample program — it prints all the numbers between 1 and 10.
First, step into your program until the next line to be executed is the
cout << nValue << " ";
line:

At this point, the variable nValue has already been created and initialized with the value 1, so when we examine the value of nValue, we should expect to see the value 1.
The easiest way to examine the value of a simple variable like nValue is to hover your mouse over the word nValue. Most modern debuggers support this method of inspecting simple variables, and it is the most straightforward way to do so.

The second way to examine the value of a variable is to highlight the variable name with your mouse, and then choose "Quickwatch" from the right-click menu.. This may have another name, such as "Inspect" or "Watch".

This will pull up a subwindow containing the value of the variable:

Now let's watch this variable change as we step through the program. First, choose "Step over" twice, so the next line to be executed is the
cout << nValue << " ";
line:

Since we are on the second iteration of the loop, nValue has been incremented once and should have value 2. Inspect it and make sure that it does!
The watch window
Using the mouse hover or quickwatch methods to inspect variables is fine if you want to know the value of a variable at a particular point in this, but it's not particularly well suited to watching the value of a variable change as you run the code because you continually have to rehover/reselect the variable.
In order to address this issue, all modern compilers provide another feature, called a watch window. The
watch window is a window where you can add variables you would like to continually inspect, and these variables will be updated as you step through your program. The watch window may already be on your screen when you enter debug mode, but if it is not, you can bring it up through your IDEs window commands (these are typically found in the view or debug menus).
In Visual Studio 2005 Express, you can bring up a watch menu by going to Debug Menu->Windows->Watch->Watch 1 (note: you have to be in debug mode, so step into your program first).
You should now see this:

There is nothing in this window because we have not set any watches yet. There are typically two different ways to set watches:
1) Type in the name of the variable you would like to watch in the "Name" column of the watch window.
2) Highlight the variable you would like to watch, right click, and choose "Add Watch".
Go ahead and add the variable "nValue" to your watch list. You should now see this:

Now chose the "Step over" command a few times and watch the value of your variable change!
Note that variables that go out of scope will stay in your watch window. If the variable returns to scope, the watch will pick it up and begin showing it's value again.
Using watches is the best way to watch the value of a variable change over time as you step through your program.
The call stack window
Modern debuggers contain one more debugging information window that can be very useful in debugging your program. The call stack window shows you the state of the current call stack. If you need a refresher on what the call stack is, see the lesson on the stack and the heap.
If you don't see the call stack window, you will need to tell the IDE to show it. In Visual Studio 2005 Express, you can do that by going to Debug Menu->Windows->Call Stack (note: you have to be in debug mode, so step into your program first).
Let's take a look at the call stack using a sample program:
03 | std::cout << "C called" << std::endl; |
07 | std::cout << "B called" << std::endl; |
Put a breakpoint in the CallC() function and then start debugging mode (via "Continue"). The program will execute until it gets to the line you breakpointed.
Although you now know the program is executing CallC(), there are actually two calls to CallC() in this program (one in CallB(), and one in CallA()). Which function was responsible for calling CallC() this time? The Call stack will show us:

The program started by calling main(). main() called CallA(), which called CallB(), which called CallC(). You can double-click on the various lines in the Call Stack window to see more information about the calling functions. Some IDEs take you directly to the function call. Visual Studio 2005 Express takes you to the next line after the function call. Go ahead and try this functionality out. When you are ready to resume stepping through your code, double-click the top line of the call stack window and you will return to your point of execution.
Putting it together
There are three primary types of problems that occur in programs. The first is due to variables being assigned the wrong values. By use of variable inspection, you can watch your variables get calculated and compare what you expected to what you got.
For example, if you saw this code:
3 | double dValue = nNumerator / nDivisor; |
You would expect dValue to have the value 2.5 after it was executed. But if you run this in the debugger, you would see that dValue is assigned the value of 2. Why? Because you did an integer division, not a floating point division. Errors like this are very hard to detect without a debugger, but very easy with one.
The second type of problem is caused by an incorrect loop condition (causing the program to loop too few or too many times). For example, you might have written the following code expecting it to print the values 1 to 10:
2 | for ( int nValue=1; nValue < 10; nValue++) |
3 | std::cout << nValue << " " ; |
But when you run it, it only prints 1 to 9. Using stepping and variable inspection, you can watch nValue as it gets incremented. When nValue is 10, the comparsion
nValue < 10
is false, and the loop terminates without having printed the value 10.
The third type of problem that new programmers often struggle with is related to the scope/lifetime of variables. This can manifest in many way, but some of the more common ones include:
- Accidentally passing function parameters by value instead of by reference, or vice-versa.
- Declaring a new variable with the same name as a function parameter, hiding the function parameter.
- Deleting some allocated memory that is referenced later (causing your program to crash).
Congratulations, you now know the basics of debugging your code! Using stepping, breakpoints, watches, and the call stack window, you now have the fundamentals to be able to debug almost any problem. Like many things, becoming good at using a debugger takes some practice and some trial and error. However, the larger your programs get, the more valuable you will find the debugger to be, so it is definitely worth your time investment!
No comments:
Post a Comment