CSCI 1300 - Exercise 2
Programming Errors and the GDB Debugger

What You'll Get from This Exercise

Last week you learned how to use emacs and bgi++ to edit and compile programs. If this editor and compiler are not installed on your machine, then you may obtain install them as shown in the box to the right. .

Now we would like you to learn about a debugger called gdb that you will use this semester. By using emacs in conjunction with gdb, you obtain a some degree of integration among your editor/compiler/debugger.

Create a Working Directory
As you did last week, make sure that the cs1300 software is installed and create a working directory. I would suggest that you use lab2 for the name of the directory so that you don't get your work mixed up with another's. So run these commands, filling in the blank with your login name:
         D:
         cd \Users
         cd ________
         mkdir lab2
         cd lab2
Once this directory is created, download these two files from the cs1300 online directory (heatwave.cxx and bugs.cxx). (You can download a file by right-clicking on the link and choosing Save File As or Save Target As or some option like that.)
Starting the Debugger within Emacs
Today you'll start by running a debugger session using the heatwave program. To get the debugger started on the heatwave program, make sure that you are in the working directory that you used in the previous exercise, and open heatwave.cxx with the emacs editor. Then recompile heatwave.cxx by giving the "ESC x compile" command. In the miniwindow, you should erase "make -k" and type this compilation command instead:

bgi++ -Wall -g heatwave.cxx -o heatwave.exe

The reason for recompiling is that the debugger requires the compilation to have an extra -g switch. I would suggest that you use -g in all your future compilations. You never know when you might need to debug! After the compilation finishes, you can start the debugger with these steps:

  1. Shut the compilation window (Ctrl-x 1, which says "give me just one window").
  2. Split the window into two (Ctrl-x 2). Both the top and bottom window will have heatwave.cxx.
  3. Move the cursor to the "other" window (Ctrl-x o). The cursor should now be in the bottom window.
  4. Start the debugger with the command: "ESCAPE x gdb RETURN". The miniwindow will print a small prompt "Run gdb (like this): gdb". You should type the name of the executable file, as shown here:

    Run gdb (like this): gdb --annotate=3 heatwave

    Then press return, and the gdb debugger starts in the bottom window.

Setting and Removing Breakpoints
When the debugger starts, the bottom screen prints a message and the prompt (gdb). With the cursor at this prompt, you can give commands to the debugger, and there are also a few special emacs commands that you can give while the debugger is running. We'll start by setting a breakpoint at the start of the main program. A breakpoint is just a line of code where the program's execution will stop. I like to stop at the beginning of the main program so that I can assess my situation and proceed from there. Here's how to set or remove a breakpoint:
  1. Move to the code window (use "Ctrl-x o" to move to the other window if you are not already in the code window).
  2. Move the cursor desired line in the program. In this case, you should move down to the beginning of the first cout statement in the main program.
  3. Ctrl-x SPACE (that is, Ctrl-x and then the space bar).
You'll see a message about the breakpoint appear in the bottom window. Or you might get an error message saying that there is no source file HEATWAVE.CXX. If you get this error message, the problem is that the file has been recorded as HEATWAVE.CXX (all capitals) but it should be heatwave.cxx (lower case). To fix this problem, quit emacs and give the rename command:
    rename HEATWAVE.CXX heatwave.cxx

Later, if you need to remove the breakpoint, move the cursor back to this line, and give the "remove breakpoint" command (Ctrl-x Ctrl-a Ctrl-d).

An alternative to "Ctrl-x SPACE" is "Ctrl-x Ctrl-a Ctrl-t", setting a temporary breakpoint (which will be removed the first time it is used).

Running the Program
Once the breakpoint is set, you can run your program. Of course, it won't run very far, since there is a breakpoint at the top of the program. Move to the bottom window and type the word "run" at the (gdb) prompt, and press return. You might get a few warnings or messages about Windows DLLs. Then you'll see messages similar to this:

Breakpoint 1, main () at heatwave.cxx:41
(gdb)

This message means that your program ran until it reached "Breakpoint 1", which was in the main() function on line 41 of heatwave.cxx. If you look in the code window at the top, you'll see that emacs placed an arrow on line 41 to indicate where the breakpoint occurred.

The prompt (gdb) in the bottom window means that the debugger is ready for you to issue another command. You can proceed in many ways. You can examine the values of variables, you can look at the runtime stack (which indicates the current sequence of function calls), you can continue executing the program. For now, you should continue executing the program by typing "cont" and pressing return. You will get the message "Continuing," and the program will execute its dialog with you. You should answer all of the program's questions until execution ends. At the end you see the message "Program exited normally" and the (gdb) prompt appears once more.

Display, Next, Print, Step
The display command allows you to display the current values of particular variables. To see how the display works, you should first restart your program, by typing the run command once again. We'll run through the program in a slightly different way, displaying the value of the variable height. In order to display this variable, type the command "display height" in the gdb window. You'll get a response such as "height=0", or maybe "height=42" or "height=126429"--you really don't know what value height will have right now because it hasn't yet been given a value.

Now we will continue executing the program, but we'll execute only one line at a time. In order to execute one line at a time, type "next" command in the gdb window and press return. Type this command once now. The next statement of your program is executed! In this program, that statement is an output statement, and the output message "How tall is your tree in feet?" appears in the gdb window. In the code window, the arrow has moved down one statement to indicate the current location of the execution.

Gdb also keeps you updated on the value of the display variable (height), which has not changed since the last time it was displayed. Finally, the (gdb) prompt appears again, indicating that the debugger is ready for another command.

At this point, you should give another next command and press return. The cursor will move to the next line and just sit there. Why did this happen? Has the debugger crashed? No! You see, the next statement of the program is an input statement to read the value of height. The program is waiting for you to type that input. Go ahead and type a number now, and press return. When you press return, the number is read, the new value of height is displayed, and the (gdb) prompt appears once more. Also, the arrow in the code window has moved down to the next statement.

Let's add one more variable to the display. Type "display volume" and press return. At this point, the volume just contains garbage because the program has not yet assigned a value to the volume. Now, continue executing the program one statement at a time, and stop when the volume does change. Keep in mind that when the (gdb) prompt appear, you must type a debugger command. When the cursor just sits on the left with no prompt, you must type input for the program. Also, keep an eye on the arrow in the code window to see which statements are about to be executed.

After the volume changes to its new value, give the cont command to execute the remainder of the program.

There are three other commands that you will find useful: (1) undisplay followed by a number n will remove item n from the display list. (2) print followed by an expression will evaluate and print the expression once (without adding it to the display list). (3) step is similar to "next", executing one statement. The difference is that the step command tries to step into the body of each function, and execute the lines of each function one at a time. Since heatwave has no functions, we're using next. (Also, step would cause problems with heatwave, since the debugger would try to step into the input >> and output << functions.)

Quitting and More About Gdb
To end gdb, type the quit command, press return, move the cursor to the top window (Ctrl-x o) and set things to just one window (Ctrl-x 1). As the semester progresses, you'll learn a lot more about gdb. You can also print the gdb reference card (in pdf format) at www.cs.colorado.edu/~main/cs1300/lab/refcard-gdb.pdf . For now however, the few commands that you know should be sufficient to finish this exercise.
Different Kinds of Errors
  • In the textbook, you'll read about three kinds of errors that a program might have. Briefly, the kinds of errors are:
    • Syntax Error: An error that occurs when the compiler does not understand what you have written. These are often small, such as a missing semi-colon.
    • Run-time Error: An error that occurs when a program is actually running. Typically, a run-time error causes a running program to stop immediately, and print an error message. For example, if a program includes an arithmetic expression x/y, and y happens to be zero—then a run-time error will occur when the expression x/y is encountered. But, this error does not occur until your program is actually running.
    • Logic Error: Sometimes your program compiles correctly, and has all the necessary directives, and runs without producing a run-time error. But, the answer that the program produces might still be wrong. These errors, also called “program bugs” are the toughest errors to find and eliminate.
    During the rest of this lab exercise, we’ll give you a program that has each of these kinds of errors. You’ll learn the typical ways of tracking down and elmininating these errors. The most important thing that you’ll do is continue to use the debugger, which is a tool to help you track down those elusive logic errors.
Finding and Correcting the Syntax Error

  • Quit emacs and then restart it on the bugs.cxx file that you copied from the cs1300 material. The file is a C++ program that I wrote. But I have made errors that you will fix. Start by trying to compile it in the usual way (ESC-X compile, and type the compile command given here:

    bgi++ -Wall -g bugs.cxx -o bugs.exe

    The compiler will give a list of errors in a message window. These are usually syntax errors.

    When several syntax errors appear in the error message box, always deal with the topmost error first. Often, if you fix the topmost error, then the others will disappear.

  • In this exercise, there are several syntax errors. The topmost error says something similar to this:
    bugs.cxx: In function `int main()':
    bugs.cxx:46: parse error before `='
    
    This means that the compiler thinks that there is a something missing on line 46 of the program. To deal with this error, make sure that the cursor is in the code window and then type Ctrl-X and press the backward quote key (usually in the upper left corner of a keyboard). Emacs will move the first error message to the top of the error window and place the cursor on line 46 of your code, where it thinks something is missing. That line looks like this:
    sum_square = height*height + base*base;
    
    But this line looks okay! There doesn't seem to be anything wrong before the '='. What is the compiler complaining about?

    When the compiler complains about a line, and you don’t see a problem, start looking at the previous lines in your program. Often the error will be one or two lines before the compiler’s guess.

  • In this exercise, the error is a few lines before line 46. Can you see this line in the code:
    cin >> base
    

    This line is missing the semicolon at the end. The compiler didn’t realize that the semicolon was missing until it got to line 46. In any case, you can fix the syntax error by placing a semicolon at the end of the statement cin >> base;

Finding and Correcting the Run-time Error
  • Once more, compile your program. It should compile with no errors, and you can run the program (from the DOS prompt). The program runs and asks you for some information. But after reading the information, the program crashes--maybe with that ominous "Illegal operation" message from Windows.

    The phrase "illegal operation" indicates that the program did something that the Windows operating system won't allow. In the case of this program, you might be able to figure out what caused the error by simply examining the program. But other times it's hard to see exactly where an error like this occurs. In such a case, the debugger can help you. The debugger lets you step through the program one line at a time, showing exactly which line is being executed.

    In order to use the debugger, move the cursor to the compile window and start the debugger with the ESC-X gdb command. When you are asked how to run the debugger, indicate that you are running "gdb bugs" and press return. When the degugger starts, go back to the code window and put a breakpoint on the first cout statement of the program.

    Now, back to the debugger window and run the program. The program will run until you reach the breakpoint, which will be highlighted by an arrow to indicate that it is about to be executed. We're ready to execute the program one line at a time. Start by pressing the n key and return. When you press this key, the compiler executes one line of code and highlights the next line. Press return again, and the compiler will execute the n-command again. Each time you press return, the debugger executes the highlighted statement and moves to the next line of your program. For example, when you execute a cout statement, the message will appear in the gdb window. When you execute a cin statement, the debugger waits for you to type the input before continuing. Warning: It is tempting to type the input when a prompt appears such as "How tall is your right triangle in inches?" But don't! That prompt appeared from the cout statement. You must wait until the cin statement is executed before you type any input.

    Keep pressing return, running the program one line at a time. In this example, stop when you reach the line:

    cout << 1/zero << endl;
    
    Press return one more time to execute this line. The debugger should intercept the illegal operation and print a message about an "exception". This is somewhat useful because you can now see exactly which line caused the error. In this case, the error is a division by zero, which results in an undefined value. Of course, we shouldn't be doing this division at all, so delete the whole line, quit the debugger, return to the code window and recompile. You'll probably still get one warning from the compiler, indicating that the variable "zero" is not used. To fix this warning, go near the top of the main program and delete the declaration of the variable zero. Then recompile and run the program once more. (You have to quit the debugger with the q command before you can recompile.)
Finding and Correcting the Logic Error
  • Finally the program is working. Or is it? There are no syntax or link errors. And there is no run-time error. But look at the answer that the program wrote:
    This is the same as 0 feet.
    
    That's not right!

    Even if a program compiles and runs without errors, you still must check that the output is correct.

    When a program has incorrect output, you can use the debugger to step through it one line at a time with the variables displayed. Go ahead and do this now, in the same way that you did before with a breakpoint on the first cout statement.

    Start the programming running, and when you reach the breakpoint, you should put displays on the five variables by typing these commands in the gdb window:

        disp height
        disp base
        disp sum_square
        disp hypotenuse
        disp feet
    
    You'll then see displays of all their current values. At this point, the values of the variables will look strange indeed. They are likely to contain garbage--whatever numbers happen to be in the computer’s memory at the moment. But, soon this will change. To see this change, run through the program, entering the number 30 for both pieces of data. After entering the second peice of data, the highlighted statement will be the assignment statement:
    sum_square = height*height + base*base;
    
    Execute this highlighted statement, and keep one eye on the variables. The value of sum_square should change to 1800 (if you used 30 for each input number). Then execute the next statement, and the value of hypotenuse will change to about 42. At this point you are about to execute the next assignment statement:
     feet = (1/12) * hypotenuse;
     
    Now, hypotenuse is around 42, so we would expect feet to be a bit less than 4. So, execute the statement, and oops! Why did feet change to zero?...

    The reason for the zero has to do with the factor (1/12) in the arithmetic expression. Remember that when C++ does division with integers, the answer is the quotient, and any remainder is thrown away. So 1/12 is actually zero. Here are two possible ways to fix the assignment statement:

    feet = hypotenuse/12;
    feet = (1.0/12.0) * hypotenuse;
    
    The first solution works okay because we are not dividing two integers (hypotenuse is a float number). The second line is another way to fix the problem becuase 1.0 and 12.0 are treated as non-integer numbers (the decimal point is enough to make them non-integer).

    Go ahead and make this last correction, and run the program once more.

A Smooth Ending to the Program
    The end of our triangle program contains these lines:
    cout << "Please press the return key to end the program." << endl;
    cin.ignore( ); // Read the return key at the end of the second input
    cin.ignore( ); // Read the return key that’s pressed to end the program
    
    These statements allow for a more elegant end to the program. The message "Please press the return key to end the program" is printed. Then a function, cin.ignore() is called twice. Each time cin.ignore() is called, a character is read from the keyboard and thrown away. The first ignored character is actually something that was typed long ago: The return key after the second input number. The other ignored character is the return key that is pressed to end the program. So, with our smooth program ending, the message appears and the program then waits for you to press the return key. After you press the return key, the program finally ends.
Copying your File to a Floppy Disk or University Machine
    At this point, you are done with the lab part of this exercise. You can exit the compiler. If you like, you can send yourself a copy of the work by e-mail. Saving the bugs program isn’t too important, but in the future you should save all your work.

Michael Main (main@colorado.edu)