GLProgramming.com

home :: about :: development guides :: irc :: forums :: search :: paste :: links :: contribute :: code dump

-> Click here to learn how to get live help <-



 
 

Development Guide Library


Debugging with Visual C++ by baldurk

Debugging is a vastly underestimated part of programming. A good ability to use the debugger is vital in being a good programmer. Most people know that being able to write syntactically correct code is just one step towards a working program. Many bugs can crop up at runtime which need to be squashed. Hopefully this DG will give people an start on how to debug programs. I'll focus on probably the most common, Visual C++. I used VC++ 6, so in .NET there may be slight changes. The idea is still the same.

I have created some very buggy code for our debugging pleasure. Although it is easy to see the bugs, and it would be possible to simply debug without a debugger, we shall use the debugger in order to learn about it, and see how it applies in different situations. As the code is corrected, I shall reprint the relevant portions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
int foo()
{
	int *foo;

	// loop through an array of 10, assigning the numbers 0 - 9 to it.
	for(int i=0; i < 10; i++)
		foo[i] = i;
 	return 5;
}

int main(int argc, char **argv)
{
	for(int i=0; i < argc; i)
		printf("argument %d: '%s'n", i, argv[i]);

	foo();

	return 0;
}

You can probably spot the bugs right away, but lets compile the code and run anyway. Pretend you've just written the code and it compiles fine. (Note: you may get compiler warnings that point out the bugs. Ignore them, as these situations may crop up without the warnings). Note, in VC++ you must be in Debug build mode.

Before we get into the real nitty-gritty of the debugging, I'm going to get you up to speed on VC++'s debugging facilities.

Step Into and Step Over are two functions which both move through one more line of code when debugging, but they do quite different things. Step Into goes one step further in the code, but if the code has a function, it will go *into* the function call. Step Over won't, it will simply skip the code. One thing you should be careful of though, is that Step Into goes into all functions, including printf, etc so be careful otherwise you might be going through code that's not your own. If you find that there's a bug in some other code, then it's 99.99% likely to be your fault, so check your code first.

Use your function stack. This is the list of the functions that have been called, in order, to get to the current line of code. You can move up and down them and check variables and debug at every level. This is a *very* useful part of VC++, and you should use it regularly.

Now you can run the code. Surprise surprise! The code fails. Let's move onto bug 1:

Bug 1

When run, the program keeps printing "argument 0: '<pathtofile>'". Obviously, it's getting stuck in the for loop. Let's find out why.

In Visual C++ you can run in debug mode up to a point, by placing the cursor at that point and either hitting CTRL+F10 or doing Build -> Start debug -> Run to cursor. Do either one now, with the cursor on the opening brace before the for loop.
Several changes will occur. The project window will disappear. The program will run, then stop. The code will have a yellow arrow in the margin on the line that the program is currently at. The output window will have a listing of current variables. We want to move the program one step on, into the for loop. Let's hit F10 (Step Over - See above for details of what this means). We are just before the for loop, you'll see the i variable appear, but it hasn't been initialised. Hit F10 once more to move to just before the printf line. Note that i has been initialised to 0. If the initialiser was more complicated, you could check that i has the correct value now. Hit F10 once more to run through the print line. Note the output is displayed in the program window. Also note that the return value is shown. This is useful if you're not calling printf, but a function where the return value matters. Hit F10 again. We are now just before the second loop through. Hit F10 once more. Gosh! i should have been incremented! Oh silly me, I forgot the ++ after i. Let's stop the debug and edit the code. Hit Shift+F5 to stop debugging, and make the change.

The main function now looks like this:

1
2
3
4
5
6
7
8
9
10
11
 
int main(int argc, char **argv)
{
	for(int i=0; i < argc; i++)
	{
		printf("argument %d: '%s'n", i, argv[i]);
	}

	foo();

	return 0;
}

Run it now to see the proper output (try passing different parameters). But what's this? Now we have an even less obvious error.

Bug 2

The program crashes after printing the arguments :( (will be different on different platforms). We don't know where exactly the error is (or we wouldn't if this was a larger project). Wouldn't it be nice if we could run through until the error occurred. But wait, we can!

Hit F5 to run through the program until an error occurs. Same as last time, yet now we are told what line the problem is on. Useful, huh? Well, not always. Sometimes, like bug 1, the problem doesn't crash the program. In that case we need to find the problem line ourselves. Anyways, let's see what the problem is...
We're inside that for loop which loops through the 10 - long array. Let's check the for line. It loops from 0. That's OK. It loops until 10, that's OK. It increases by one each time. that's OK. Let's now look at the variables. i is fine, it's 0 as it's meant to be. So what's foo? aha. Foo is the problem. You see that foo is 0xcccccccc. This is a special pointer (in debug mode) that shows that you haven't initialised it. (As said before, there is probably a warning to this effect). Actually, it means that it isn't pointing at anything, and in this case it means we haven't allocated space for the array. So let's do that. Stop debugging, and change the code.

Should be like this now:

1
2
3
4
5
6
7
8
9
10
11
 
int foo()
{
	int *foo = new int[10];

	// loop through an array of 10, assigning the numbers 0 - 9 to it.
	for(int i=0; i < 10; i++)
		foo[i] = i;

	delete[] foo;
	return 5;
}


Conclusion

There you go, a quick and nasty guide to debugging. There is much much more, so consult the msdn to find out more. It will give you enough to debug the most complex of errors. Experiment in your own apps next time you get a bug :).

Notes: Common Errors

Although not technically debugging, you can prevent bugs by being aware of common causes. Here is a list of common errors you might experience.

Differences between Debug and Release - often errors appear in one but not the other. You should investigate two things. Firstly, anything that could be affected by optimisation. Secondly, arrays, especially dynamically allocated ones. The memory code works differently, and that can cause problems. Debug is sometimes less strict as Release.

Overwriting array boundaries - If you loop from 0 to 10 (including 10), and you have an array initialised with 'int foo[10]', you'll have a problem. In C and C++, the array[10] line means 10 elements. However, it starts numbering from 0, so it only goes up to 9. So if you try to write to element 10, you'll get an access violation type error. You need to be careful of this.

Not initialising arrays - I often see people who expect to be able to use pointers as arrays just off the bat. This won't happen. You must allocate space, and when you're finished, you must delete it.

Infinite loops - Not so common, but worth an honourable mention at number 3. It is occasionally necessary to write a loop going from 10 to 0. like this:

for(int i=10; i >= 0; i++)
{ /* ... */ }

Don't see anything wrong with it? look again. Perhaps you are so used to increasing loops, that you didn't notice that i is being incremented each time. In a decreasing loop, this will go on forever, and possibly cause one of the above bugs.