Getting Started with the Turbo C++ 3.0 Debugger


A debugger is an efficient, flexible, and easy-to-use tool for viewing a program as it executes. It is superior to sprinkling output statements throughout a program. A debugger will allow you to stop program execution, view and modify variable values, and re-run the program from the point it was stopped. All this can be achieved without modifying the program itself.

This article will serve to get you started with the Turbo C++ debugger. Because this article is only meant as an introduction, some of the more advanced features will not be discussed here; the interested programmer should refer to the on-line documentation or the reference manual for more information.

On-line documentation may often be read by highlighting a menu item (using the up-arrow or down-arrow keys) and pressing F1, by choosing Help within a dialog box, by highlighting a dialog box item (using the Tab key) and pressing F1, or by using the Help menu. When reading Help text, you may often get additional help on specific features by ``clicking'' highlighted words in the text and then choosing the Cross ref button in the Help dialog box.

Setting up for Debugging

In order to use the debugger, the compiler must first be set-up to include debugging information in the executable program. The Integrated Development Environment (IDE) should be configured to handle this for you automatically, but if the debugger appears to be ``broken'' you should check these settings. Unless the settings have been changed from their default values, you should not modify them.

Open the Options|Debugger dialog box. Source Debugging should be on. The Smart Display Swapping option should be set. Inspectors should be set for Show Inherited, Show Methods, and Show Both. Program Heap Size should be 64K bytes.

Debugging Actions

The most common debugging actions are single-stepping or running (from the beginning or from the last statement executed) a program, setting and using breakpoints, and looking at and modifying variables. Let's look at each of these in turn.

Note that many of these actions have ``short-cut'' keys associated with them. You can use the short cuts to avoid opening menus to choose actions.

Running Your Program

You are probably most familiar with using Run|Run to start your program from the beginning. However, Run|Run is really used to start your program from the point of last execution. Only if the program had completely finished or was aborted does Run|Run pick up at the beginning.

There are two ways to single-step (execute only one statement at a time) your program. Run|Step over executes the next statement (this is the statement highlighted by the run bar), treating subroutines (function or procedure) as if they were a single statement. Thus, you will not be able to watch the individual statements of your subroutines execute. Run|Trace into executes the next statement, treating subroutines as if they were collections of statements. With Run|Trace into you can watch your subroutines execute line-by-line. This will generally only work for subroutines which you write. You cannot trace into library functions.

Anytime you would prefer to return to normal execution rather than single-stepping, choose Run|Run. If you are in the middle of your program's execution and want to return to the beginning, select Run|Program reset. If there is no run bar (i.e., you are at the very beginning of your program) you may single-step to start the program at its first statement (i.e., the first statement of main()).

How do you determine whether to use Run|Step over or Run|Trace into? Firstly, you need to know that you can switch freely between the two. Once you start using one of them, you may still use the other. Secondly, the choice between the two should be governed by how much confidence you have in any subroutines which would be executed in the next statement (if there are no subroutines in the next statement, then there is no difference between Run|Step over and Run|Trace into).

Run|Go to cursor is a primitive breakpoint, so I won't discuss it; see the on-line documentation.

Setting and Using Breakpoints

If you suspect that there is a bug within a statement which is the 1,000th program statement executed, it's not terribly convenient to single-step to it. Instead, you would like to run the program until you get ``near'' the suspect statement or subroutine and then single-step the program. You would like to ``break'' (suspend) program execution at a particular point within the program --- a breakpoint. From that point, you can use Run|Step over or Run|Trace into.

Breakpoints are set by moving the cursor to an executable statement where you would like a break to occur and choosing Debug|Toggle breakpoint. This line is then highlighted. Repeat this process until you have all the breakpoints which you want. Choosing Run|Run causes the program to execute until the first breakpoint is encountered. Choosing Run|Run again will run the program until the next breakpoint (possibly the same one as before) occurs. Choosing Debug|Toggle breakpoint on a line which has a breakpoint already set removes the breakpoint.

Debug|Breakpoints contains several advanced breakpoint options. Among these are editing breakpoints, setting qualifier conditions for breakpoints, and setting a skip count for a breakpoint. Refer to the on-line documentation for details.

Viewing and Modifying Variables

Once you've gotten the program running and stopped at a particular statement (by single-stepping or by using breakpoints), you can look at the values of variables or expressions (actually, a variable is just a simple expression) and modify variables. Obviously, any terms which you reference within an expression must be defined within the current context. Expressions may not contain function calls. In Turbo C++, #define'd constants may not be a part of the expression.

To check expression values, choose Debug|Evaluate/modify. Type an expression in the Expression Field and press the Return key (or press the Evaluate button). The value of the expression will be displayed in the Result field. The on-line documentation contains information for optional result formatting and for displaying several elements of an array.

You can use the Tab key to move the cursor to the New value field and enter a new value for a simple variable (an integer, a single array element, a pointer, a single field within a structure or record, etc.).

Debug|Evaluate/modify can be tiresome to use if you would like to watch what happens to a variable each time a statement executes. Use Debug|Watches for this (refer to the on-line documentation). Turbo C++ has a mechanism for easily viewing compound variables (arrays and structures). This mechanism is Debug|Inspect (again, refer to the on-line documentation).

Going Further

As mentioned previously, the on-line documentation provides detailed descriptions of all the debugging commands I've touched on here.

The only debugging command which I haven't mentioned so far at all is for displaying the call stack. The ``call stack'' is a listing of the currently active subroutines. In Turbo C++ the call stack is displayed by choosing Debug|Call stack.

Written by T. P. Kelliher for classes using the Borland Turbo products. Please bring errors, comments, improvements, accolades, cookies, etc. to T. P. Kelliher.

Thomas P. Kelliher
Thu Feb 22 11:16:33 EST 1996
Tom Kelliher