University of Pittsburgh
Summer 2001
CS401:  Introduction to Computer Science
Lab 1:  Debugging in Borland, and more
INTRODUCTION

As you know, when compilers first became available, the only way to use them was from the command line.  This makes sense considering the fact that graphical user interfaces didn't make their way to forefront until the early 80's.  IDEs (like Borland C++) are more than just an editor, compiler, and linker rolled into one package, and in this lab we will begin to see what other tools are available to us.  Certainly IDEs are around for some reason or another - this lab will help you get a glimpse of what they can do.

This lab is important for you even if you don't plan on using Borland C++ after this semester, or are using a different environment at home.  For example, at some point in your life, you've probably used some popular word processor like Microsoft Word.  Now, assuming you got fairly proficient at using it, you would then be able to pull up pretty much any word processor and still be productive.  IDEs are similar:  once you get good at using one graphical debugger, for example, you will be able to pick up any other one with much less effort. 

So, what is a debugger and why do we need it?  When you type in your C++ program and compile it, you sometimes get errors. These are called syntax errors and indicate that there is something wrong with the physical structure of your C++ code. Hopefully the message given by the compiler will help you to fix the mistakes and thus eliminate the errors (with some help from your textbook and notes).  Take note, compiler error messages are helpful about 50% of the time, if you're lucky!  If the compiler tells you that you are missing a semi-colon, for example, and you can't determine where one is missing in less than 2 seconds, then you should ignore the error message and look for the real cause of the problem.  Adding semi-colons everywhere they will fit (something some people actually do!) will only prolong your frustration.  The compiler does its best at categorizing your errors, just don't assume anything about its accuracy.

Unfortunately, especially in large programs, most of the problems are not of the syntax variety, but rather lie in the logic of the program (or maybe we should say "illogic" of the program). In other words, the program is structured in a legal way as far as the compiler is concerned, yet it does not accomplish what was intended. These logic errors are what are commonly referred to as bugs. Logic errors are often quite difficult to detect in programs, since the programmer needs to know what the program is expected to do in order to determine that something is wrong. Even after detection, logic errors are difficult to locate and eliminate since in large programs it can be extremely difficult to determine exactly where and how the logic error was made.

The debugger can be very helpful in locating and correcting logic errors, by allowing the programmer to check variable values, stop execution at certain points, and step through the program a single instruction at a time. You will next try some of these debugging features in the Borland C++ environment.

Finally, it should be noted that the better you plan and organize your program, the less you will need a debugger.  It's just like building anything else:  the better your car was built and maintained, the less trouble you'll have later;  same thing for houses, clothes, etc. 
 

OBJECTIVES

After you complete this lab, you should be able to do the following things in Borland C++:
  • step through a program in the debugger
  • set breakpoints in the debugger
  • watch variables in the debugger
  • see how command line paramters can be passed in
  • use context sensitive help
WHAT TO DO

Take note:  you will be running the debugger while simultaneously, the program will be running in its own window.  You'll have to go back and forth between these two windows.  One way to do this is to click on the appropriate button on the task bar (at the bottom of your screen), or you can use the key combination ALT-TAB, which lets you rotate through the various running tasks.  Your TA can help you figure this part out if you have trouble.
 

Stepping through a program

The debugger can show you exactly what lines of code are executed and in what order.  In addition, you can control when the program advances to the next statement.

  1. Go ahead and start up Borland C++ V5.02 and create a new project called euclid.ide.  You should refer to Lab 0 to remind yourself how to create projects.
  2. Click on the euclid.cpp node in the project window and enter the following C++ program (don't worry if you don't understand some of it - we haven't covered loops yet, for example):
  3. // euclid.cpp
    // <your name>
    // Euclid's GCD algorithm

    #include <iostream>
    #include <string>
    #include <conio>         // gives access to getch()
    using namespace std;

    int main() {

      cout << "Enter two integers (separated by a space) and I will compute their" << endl;
      cout << "Greatest Common Divisor : ";

      int m,n,r;
      cin >> m >> n;

      cout << "\nGCD(" << m << "," << n << ") = " ;
      while (n != 0) {
         r = m % n;
         m = n;
         n = r;
      }

      cout << m << endl;
      cout << "press any key...";
      getch();              // waits for a character to be entered
                            // it holds the window open

      return 0;
    }

  4. Try using the "lightening bolt" button (if you haven't used it before) to run the program a few times with varying input.  Get a feel for the program's behavior.
  5. When pointing into the program window, click the right mouse button and select the Statement Step Over option from the menu. Notice that this menu option has a shortcut key <F8>. 
  6. Step through the execution of your program by using the <F8> key. Enter the input into the execution window when prompted. Note that each line of the program is highlighted as it is about to be executed and you can’t step over a line that requests input until that input is provided. This requires you to move from one window to another. When the execution is finished, the output window for the program will go away. This occurs after the getch() function has gotten a character from the user.
  7. For programs with separate functions (which we have not yet discussed), the <F7> key can be used to "Trace Into" function calls as they are made. You will be able to try this once we use functions in our programs. For the euclid.cpp program, <F7> will be no different than <F8>.
  8. When you are finished stepping or want to start stepping again from the beginning, you must first terminate the current process. You can do this by selecting the Terminate Process option from the Debug menu.


Monitoring an expression or variable during execution

You can keep track of a variable (and watch its value change as the program runs) by watching it.  This is especially useful because you usually have an idea of what a variable should have in it, so this will show you what actually happens to that variable.

  1. Choose Add Watch from the Debug menu (or CTRL-F5). 
  2. On the "Expression" line type n and then click on "OK". A window should appear with n in it. 
  3. To add another watch click on n in the "Watch" window to highlight it, then single click the right mouse button to get another menu. 
  4. Click on Add Watch, but this time enter m for the "Expression". 
  5. Do this again and add the variable r.
  6. All three watch values should now indicate <Undefined Symbol> since the program is not being executed yet.
  7. You may want to rearrange and/or resize the windows so you can see both the source file and the watch window.  This is important, so if you can't figure this out, ask the TA.
  8. Step through the program as you did in the previous section, but this time watch the "Watch" window as you do so (it is appropriately named!). Note that initially the values for n and m and r are totally bogus -- they have not been initialized. See how the values change as the program execution progresses.  Very cool.
  9. Close the watch window when you're done.


Selecting points at which to pause execution

Usually you will have some idea where the program is screwing up, and since there is no need to step through the entire program every time, you can tell it where to begin waiting for you to single step.  These are called breakpoints.

  1. Click the cursor on the line in the program where you wish to begin single-step mode and press F5 (or left click the left border next to the line where you want your breakpoint to be set). This line will now be highlighted, indicating the presence of a breakpoint. A good line to choose is the second output statement.
  2. To execute to this breakpoint, hit CTRL-F9 (or "Run" from the Debug menu). 
  3. Use <F8> to step through the code after you hit the breakpoint (go for about 4 or 5 steps).  Note:  you'll be giving input for the running program in the meantime.
  4. Set another breakpoint on the line n = r at the bottom of the loop body. 
  5. To "run" to the next breakpoint, use CTRL-F9 again.  Do this a couple of times.
  6. If your program is not complete yet, remove all breakpoints in the program you just set and hit CTRL-F9 again.  To remove breakpoints, just click on that line and hit F5 again.  You are toggling the breakpoint.
Breakpoints will be very helpful in debugging large programs, where much of the program is known to be correct and thus can be executed normally. A breakpoint can then be placed at a point where execution becomes questionable, and the program can be traced in single step mode from there.
 

Printing your source code

Choose Print from the File menu and print a copy of euclid.cpp.  Make sure you have put your name in the header comment.  You'll be turning this in with your worksheet.
 

Passing information through command-line parameters

This program will show you how to supply the input from the command-line.

  1. Close euclid.ide and create a new project called euclid2.ide (make sure you don't miss any steps!). 
  2. Enter the following code into euclid2.cpp.  Again, do not worry if you don't understand some of the details (we will cover functions and arrays later, which is what you need to know about to completely follow this). 
  3. // euclid2.cpp -- Euclid's GCD algorithm
    // <your name>
    // This version includes command line parameters

    #include <iostream>
    #include <string>
    #include <conio>         // gives access to getch()
    using namespace std;

    int main(int argc, char *argv[]){ 

      int m,n,r;
      m = atoi(argv[1]);   // get m,n from the command line
      n = atoi(argv[2]);

      cout << "\nGCD(" << m << "," << n << ") = " ;
      while (n != 0) {
         r = m % n;
         m = n;
         n = r;
      }

      cout << m << endl;
      getch();

      return 0;
    }

  4. Compile your program (but don't run it) by pullin down the Project menu and selecting Make All.  Just ignore the warnings.
  5. We'll run it twice.  The first time, start up a command window, cd to the "users" directory, use dir to look for your executable file, then run it with the command: c:\users>euclid2 36 180 
  6. Now we'll see how to provide command line parameters from within Borland.  Select Debug-->Load and then in the Arguments box enter the two integers separated by a space. 
  7. After clicking on OK, if a CPU window opens, just close it by clicking on the X in the upper right corner. 
  8. Select Debug/Run and the arguments will be passed into the variables m and n inside the program.
LOOK IT UP

euclid2.cpp contains a function call to atoi().  Use the help facility to find out what atoi() does.  The easiest way to do this is to highlight atoi (either occurrence), then do a CTRL-F1.