| EECS 110: Visual Studio Debugging Notes |
Home General Info Help |
Lectures Homework |
This tutorial was written for Visual Studio 6.0. Some of the images included here look slightly different in .NET. I'm planning to update it, but until then, refer to this site for additional detail. But do read this page first, as it includes some background material.
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 Visual Studio to find and correct them.
These are the errors that are reported by the compiler. Usually they are simple syntax errors such as misspelled words or missing punctuation.
Let's try to compile the following program and see what happens:
int main () {
printf("Hello World!\n)
return 0;
The compiler gives us the following warnings and errors:
D:\test.c(2) : warning C4013: 'printf' undefined; assuming extern returning int D:\test.c(2) : error C2001: newline in constant D:\test.c(3) : error C2143: syntax error : missing ')' before 'return' D:\test.c(4) : fatal error C1004: unexpected end of file found
First, we have a warning on line 2. It means that the compiler could not
find a definition for function printf. However, it is possible that
the definition of this function is in another file of the same project. Since
it is
the job of the linker to "link" together the different files of a project, the
compiler assumes that the problem will be solved at the linking stage. This is
why it gives us only a warning. In this case, however, we have made
a mistake. The function printf is defined in the standard library
stdio.h which should have been included in the program.
We also have an error on line 2:
printf("Hello World!\n)
The error message suggests that a newline occurred when it wasn't expected. We
notice that the message that is to be printed is not enclosed in double quotes.
This caused the compiler to think that Hello World!\n) is all part
of the message but then, when the line ended and the message did not, the
compiler got confused. To fix this we need to insert the closing double-quote
at the end of the message.
Now we have an error on line 3. It was expecting a closing parenthesis
before return but it wasn't there. This is a propagated error. The
closing parenthesis is there but, because we forgot to close the quotes, the
compiler thought that it was part of the message to be printed. So we can ignore
this error.
Finally, we get to the last message. This is easy to figure out. The program
seems to have ended when it shouldn't have: we missed the closing brace for the
main function.
Let's see what the corrected program looks like:
#include<stdio.h>>
int main () {
printf("Hello World!\n")
return 0;
}
Compile it:
D:\test.c(5) : error C2143: syntax error : missing ';' before 'return'
Oops! We have another error: we forgot to terminate the printf
statement with a semicolon. The final, correct program is now:
#include<stdio.h>
int main () {
printf("Hello World!\n");
return 0;
}
These errors are caused when you forget to define or implement a function.
Let's compile and link the following program:
int main () {
myfunction();
return 0;
}
The compiler will give us a warning about myfunction but will compile
the program successfully. When we link, however, we get:
test.obj : error LNK2001: unresolved external symbol _myfunction Debug/test.exe : fatal error LNK1120: 1 unresolved externals
This is the linker's way of saying "I could not find a definition of function
myfunction anywhere!".
What does anywhere mean? The linker first looks at the other files
of the project (if there are any). If it does not find the definition there,
it looks at the files in the Include directory. This is a directory
that contains the files where the standard C library functions are defined.
Note that the linker only looks there because we have set up Visual Studio
this way. Otherwise it would only look at the other files of the project.
Would we get a linker error if we had not included stdio.h in the
program of the first example? The answer is no, because stdio.h is in
the Include directory and the linker would find printf
when it looked there.
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". To see a picture of this first bug, which had the honor of being pasted onto the journal, click here.
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.
A simple but often useful method of debugging is by
inserting printf statements in various places in the program. This
enables us to:
Now, we will learn how to use the Visual C++ debugger to find the bugs in the following program:
/* *******************************************
* This program reads five integers from the *
* keyboard and then computes and prints *
* their average on the screen. *
******************************************* */
#include<stdio.h>
#define SIZE 5
int main () {
float sum, ave;
int i;
int num[SIZE]; // array to hold the numbers
for (i=1; i<=SIZE; i++) {
printf("Enter a number: ");
scanf("%d", &num[i]);
}
for (i=1; i<=SIZE; i++)
sum=sum+num[i];
ave=sum/SIZE;
printf("The average is: %f\n", ave);
return 0;
}
This program compiles and links successfully. Let's see what happens when we execute it:
Enter a number: 1 Enter a number: 2 Enter a number: 3 Enter a number: 4 Enter a number: 5 The average is: -21474833.600000 Press any key to continue
There is obviously something wrong. Let's use the debugger to find out what:
The following figure shows the Build and Debug toolbars. To make them appear on your Visual C++ desktop, right-click on the blank space next to the existing toolbars and select Build Minibar and then Debug.
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).
The symbol for the breakpoint is :
Let's say we decide to set a breakpoint right before the
second for-loop. Move the cursor to the beginning of the
line for (i=1; i<=SIZE; i++)
and click on the breakpoint symbol. A red dot will appear on the margin left
of the cursor:
Now, to run the program until the breakpoint, click the Go button:
You will need to enter the five integers for the program to get there. Notice
the yellow arrow that shows you where in the program you currently are.
Once we have reached the breakpoint, we want to check the contents of the array
num to make sure it is fine. Do do that, type the name of the array,
num, in the Watch window (lower right):
The value shown is the address of the beginning of the array. Click the plus
sign to the left of num to expand the array and see its contents:
Aha! The first element of the array is junk. Why did this happen? Something must have gone wrong earlier on. Let's remove this breakpoint since it has served its purpose: Move the mouse cursor on the line where the breakpoint is, right-click and select "Remove breakpoint".
Now, we are going to go through the program one step at a time.
To step through the program, we use the following icons:
Step into a statement.
Used to execute a single
statement. If the statement contains a function call, it will step into the
function.
Step over a statement.
Used to execute a single statement and jump to the beginning of the next one.
Step out of a block.
Used to step out
of a block.
Run to the cursor. Executes all statements
up to where the cursor is.
Note: If you click the Step into button at the wrong moment you may
step into the code for a standard function (such as printf) and
assembly code will then be displayed:
To exit this code, just click the Step Out icon.
Now, let's stop the debugging process so that we may restart. Click the
Stop Debugging button:
We are ready to start the step-by-step execution of our program. Click the
Step Over button until you get to the first for-loop. Make sure you
have num in the watch window and that it is expanded.
Step-Over a couple of times until you get to the scanf. Note that
once you are there, when you try to step-over again, you can't. This is
because scanf is waiting for input from the keyboard. Bring the
I/O window to the top and type 1.
Look at the watch window. num[1] has become 1 whereas all the other slots of
the array contain junk. But the first element
of the array, the one that should contain the value we entered, is num[0], not
num[1]. Why did the number go into num[1]? Let's look at the leftmost watch
window which shows the current value of i. It is 1, when it should
have been 0. The debugger told us where the problem is (i contains the
wrong value when the loop begins), now our brain has to figure out why this
happened and how it can be fixed. The answer in this case is simple. The loop
should be going from 0 to SIZE-1 and not from 1 to SIZE because the C language
uses zero-indexing for arrays.
There are other errors in the program. See if the debugger can help you find them.
As you saw above, there are two watch windows. The leftmost is the one that
shows the values of the most current variables (those in the current scope).
Use the rightmost one to watch the values of other variables or to perform
computations. For example, you may want to watch the value of
num[0]+num[1]+num[2]:
Note that the value of i is red. This happens whenever the value
of a variable is updated (to attract your attention).