Debugger Tutorial page 1 Tutorial: Debugging in Dev C++ Introduction There are several types of errors that can occur at different stages of the programming process: compiler errors, linker errors and run-time errors. In this tutorial we will discuss each type and explain how you can use the tools provided by Dev C++ to find and correct them. Compiler Errors Description These are the errors that are reported by the compiler. Usually they are simple syntax errors such as misspelled words or missing punctuation. Example Let's try to compile the following program and see what happens: int main() { cout << "Hello World!" return 0; } The compiler gives us the following warnings and errors: Line 3 File c:\hello.cpp c:\hello.cpp 4 c:\hello.cpp Message In function 'int main()': 'cout' undeclared (first use this function) (Each undeclared identifier is reported only once for each function it appears in.) expected ';' before "return" First, we have a warning on line 3. It means that the compiler could not find a definition for cout. We have made a mistake. The cout is defined in the standard library iostream which should have been included in the program. We will also need to use the standard namespace. We also have an error on line 4. This is easy to figure out. We left off the semi-colon on the previous line. Let's see what the corrected program looks like: #include <iostream> using namespace std; int main() { cout << "Hello World!"; return 0; } ©2008, Regis University last mod 1-31-2008 Debugger Tutorial page 2 This program should compile without any errors. Linker Errors Description These errors are caused when you forget to define or implement a function. Example Let's compile and link the following program: void myFunction(); // prototype int main() { myFunction (); return 0; } The compiler will give us the following linker error: Line File Message c:\temp\ccURgaaa.o(text+)x2b) In function 'int main()': [Linker error] undefined reference to 'myFunction()' c:\temp\ccURgaaa.o(text+)x2b) ld returned 1 exit status This is the linker's way of saying "I could not find a definition of function myFunction". Although we provided a prototype for function myFunction, we never actually defined the function. Run-time Errors Description These are logical errors, that is, errors in the design of your program. They occur when you run your program and they are the most difficult to find. The only indication that there is an error is a wrong output or a fault that causes your program to crash. A program may run fine for years and then suddenly crash on some input because of a logical error. That's why you should always test your programs extensively. The process of locating the source of logical errors and correcting them is called debugging. The term was coined by Admiral Grace Hopper in the 1940s. According to her journal, when they opened up a computer to try and find out why a program was not working, they discovered a moth that had gotten inside and caused the problem. Since then, initially hardware and then logical errors have been called "bugs". ©2008, Regis University last mod 1-31-2008 Debugger Tutorial page 3 A debugger is a tool that helps you find logical errors. It enables you to execute your program one line at a time or in sections, to stop execution at a specific point and to check the values of variables as you go along. This way, you can better understand how your program behaves (as opposed to how you think it behaves) and find the exact location where the problem is. What the debugger cannot tell you is why the problem occurs. This is something you have to figure out yourself. Debugging Method #1 A simple but often useful method of debugging is by inserting cout statements in various places in the program. This enables us to: Find out whether a specific part of the program is ever reached during execution. Print out the values of different variables at specific points in the program. Example Let's say we have a want to read numbers from the user until the user enters 0. And we want to find the product of all the integers. Say the user enters the numbers 2, 3, 4 and 0. We would expect an output product of 24. Here is the program: #include <iostream> using namespace std; int main() { int num, product = 1; do { cout << "Enter a number (0 to exit): "; cin >> num; product *= num; } while (num != 0); cout << "Product = " << product << endl << endl; system ("PAUSE"); return 0; } When we run the program, the output is: Product = 0 Press any key to continue . . . We can insert a cout statement right after the cin >> num statement, so that each number being mulitplied into the product will be displayed (to verify the number read in is correct). ©2008, Regis University last mod 1-31-2008 Debugger Tutorial page 4 And we could also insert a cout statement right after the product is computed each time. do { cout << "Enter a number (0 to exit): "; cin >> num; cout << " Multiply by " << num << endl; product *= num; cout << " Running product " << product << endl; } while (num != 0); Now when we run the program, we can see that the last number multiplied is the 0, which was supposed to indicate a stopping point: Enter a number (0 to exit): Multiply by 2 Running product 2 Enter a number (0 to exit): Multiply by 3 Running product 6 Enter a number (0 to exit): Multiply by 4 Running product 24 Enter a number (0 to exit): Multiply by 0 Running product 0 Product = 0 2 3 4 0 Press any key to continue . . . The while loop can be fixed as follows: do { cout << "Enter a number (0 to exit): "; cin >> num; if (num != 0) product *= num; } while (num != 0); ©2008, Regis University last mod 1-31-2008 Debugger Tutorial page 5 Debugging Method #2 Another method is to use the debugger that is built into the Dev C++ IDE. We will now learn how to use the Dev C++ debugger to find the bugs in the following program: Take a messed-up version of the fibonacci program presented earlier this week (week 5): #include <iostream> #include <iomanip> using namespace std; int main() { int num1, num2, seventh, newTerm, sum; // First & second numbers in series // Seventh term // Next term in the series // Running sum of elements cout << "Enter the first two numbers of the series, separated by a space: "; cin >> num1 >> num2; cout << endl << endl << "The first 10 numbers in the Fibonacci series" << endl; cout << "beginning with " << num1 << " and " << num2 << " are:" << endl; cout << setw(5) << num1 << setw(5) << num2; sum = num1 + num2; // initial sum (first 2 terms) for (int count = 3; count <= 10; ++count) // generate/display 3rd - 10th terms { if (count == 7) seventh = newTerm; // save seventh term newTerm = num1 + num2; sum += newTerm; cout << setw(5) << newTerm; num1 = num2; num2 = newTerm; } cout << endl << endl; cout << "The sum of the first 10 elements is " << sum << endl; cout << "The seventh term (" << seventh << ") times 11 is " << seventh * 11 << endl; cout << endl << endl; system ("PAUSE"); return 0; } This program compiles and links successfully. Let's see what happens when we execute it: Enter the first two numbers of the series, separated by a space: 4 5 The first 10 numbers in the Fibonacci series beginning with 4 and 5 are: 4 5 9 14 23 37 60 97 157 254 The sum of the first 10 elements is 660 The seventh term (37) times 11 is 407 ©2008, Regis University last mod 1-31-2008 Debugger Tutorial page 6 Press any key to continue . . . There is obviously something wrong, because the sum of the first 10 elements was supposed to be equal to the seventh term times 11. Let's use the debugger to find out what: Setting up the debugger (only the first time you use the debugger) In order for the debugger to run correctly, you must check the following settings in your Dev C++ IDE. From the IDE main menu, choose Tools, then Compiler Options. Click on the Settings tab. Then click on Linker in the left side of the window. Under "Generate debugging information" in the right side of the window, make sure the answer is "Yes" (change to "Yes" if necessary). Click on Optimization in the left side of the window. Under "Perform a number of minor optimizations" in the right side of the window, make sure the answer is "No" (change to "No" if necessary). Click the OK button at the bottom of the window. Setting breakpoints A breakpoint is a point in the program where you want the execution to stop temporarily so that you can examine the values of variables. It allows you to run through portions of the program that are correct and concentrate on those that aren't (or haven't been checked yet). Let's say we decide to set a breakpoint within the if statement. Move the cursor to the beginning of the line seventh = newTerm; and click in the tan strip to the left of the line. A red dot with a checkmark will appear in the margin and the line will be highlighted in red: ©2008, Regis University last mod 1-31-2008 Debugger Tutorial page 7 Now, to run the program until the breakpoint, choose Debug, and Debug again from the main menu, OR click the Debug button (purple checkmark) OR press the F8 key All of this actions will start the debugger, and run the code until it hits a breakpoint. For this program, you will need to enter the two integers for the program to get to the breakpoint. Once we reach the breakpoint, the next line to be executed will be highlighted in blue. Once we have reached the breakpoint, we want to check the contents of the variable newTerm to make sure that it contains what we think it should. Click on the Add Watch icon at the bottom of the screen A "New Variable Watch" window will appear. Type the name of the variable, newTerm, into the window and press the OK button: The value of the newTerm will now show up under the debug tab in the frame to the left of your code: ©2008, Regis University last mod 1-31-2008 Debugger Tutorial page 8 The newTerm value is 37. But wait! That is the value for the SIXTH term, not the seventh term! Why did this happen? Something must have gone wrong. First, let's stop the debugging process so that we may restart. Clicking the Stop Execution button Next, remove this breakpoint since it has served its purpose. Move your mouse to the line where the breakpoint is and click on the red dot with the green check in it Now, we are going to go through the program one step at a time. Stepping through the program To step through the program, we use the following icons at the bottom of the screen: Next Step is used to execute a single statement and jump to the beginning of the next one. If the statement contains a function call, it execute the entire function in one step. Step Into is also used to execute a single statement. But if the statement contains a function call, it will step into the function, so you can run the function line by line. Continue executes all statements up to the next breakpoint. Run to Cursor executes all statements up to where the cursor is. Warning: If you click the Step Into button at the wrong moment (on a line that calls a built-in function) then you may step into the code for that standard function (such as sqrt) and its code will then be displayed: ©2008, Regis University last mod 1-31-2008 Debugger Tutorial page 9 To exit this code, just click the Next Step icon. We are ready to start the step-by-step execution of our program. First click your cursor anywhere within the code of the first line of code within the main function. The line will be highlighted in LIGHT blue: Then click on the Run to Cursor button to start the debugger. The debugger will stop on the first EXECUTABLE line of the main function (i.e. it will skip the variable declarations): Notice that the dark blue highlighting the NEXT line to be executed. ©2008, Regis University last mod 1-31-2008 Debugger Tutorial page 10 Click on the Next Step button until you get to the cin line. Note that once you are there, when you try to Next Step again, you can't. This is because cin is waiting for input from the keyboard. Click on the button with the DOS screen icon Type: 4 5 in your Task Bar to bring up the I/O window. and press ENTER. Click back on the IDE window to activate it. Then press Next Step, until you enter the for loop: Let's add a watch on the count variable. Click the Add Watch button, type in count, and click OK. Note: You can also add a variable to the watch window by "floating" your cursor over the name of the variable within the source code side of the window. Try adding a watch on one of the other variables using this method, before you continue. Now press Next Step until the count changes to 7 in the debugger watch window. At this point, our newTerm should be 60 ( the value of the 7th term). But it is not. It is still 37 in the watch window. The debugger told us where the problem is (newTerm contains the wrong value when it is saved). If we are done, we can press Stop Execution, to stop the debugger session. Now our brain has to figure out why this happened and how it can be fixed. The answer in this case is that we must wait to save the seventh term until AFTER we compute the newTerm for this loop. ©2008, Regis University last mod 1-31-2008 Debugger Tutorial page 11 So modify the for loop to be: for (int count = 3; count <= 10; ++count) // generate/display 3rd - 10th terms { newTerm = num1 + num2; sum += newTerm; if (count == 7) seventh = newTerm; // save seventh term cout << setw(5) << newTerm; num1 = num2; num2 = newTerm; } Debugger Notes: - You can add watches to any variables within your code. However, their values will only appear when you are stepping through code that is in the variable's scope. - You can set multiple breakpoints within your code. Execution will pause each time a breakpoint is encountered. - Occasionally the Dev C++ debugger will lock up. If this happens, just press the Stop Execution button and start again. ©2008, Regis University last mod 1-31-2008