EECS 110: Visual Studio Debugging Notes

Introduction

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.

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 () {
   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;
}

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:

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.


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". 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:

Example

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:

Setting up

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.

VS toolbars

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).

The symbol for the breakpoint is : VS breakpoint symbol

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:

VS breakpoint mark

Now, to run the program until the breakpoint, click the Go button: VS 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):

VS watch window

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:

VS watch variable value

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.

Stepping through the program

To step through the program, we use the following icons:

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:

VS assembly code

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: VS stop 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.

The Watch Windows

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]:

VS watch expressions

Note that the value of i is red. This happens whenever the value of a variable is updated (to attract your attention).


Valid HTML 4.01 Strict