Dealing with Bugs - Fixing Problems - C++ All-in-One For Dummies (2009)

C++ All-in-One For Dummies (2009)

Book III

Fixing Problems

image

Contents at a Glance

Chapter 1: Dealing with Bugs

It’s Not a Bug, It’s a Feature!

Make Your Programming Features Look Like Features

Anticipating (Almost) Everything

Avoiding Mistakes, Plain and Simple

Chapter 2: Debugging a Program

Programming with Debuggers

Debugging with Different Tools

Chapter 3: Stopping and Inspecting Your Code

Setting and Disabling Breakpoints

Watching, Inspecting, and Changing Variables

Chapter 4: Traveling About the Stack

Stacking Your Data

Debugging with Advanced Features

Chapter 1: Dealing with Bugs

In This Chapter

Distinguishing bugs from features

Anticipating every move the user makes

Avoiding mistakes the easy way

Dealing with errors

Who knows whether it’s true, but as the story goes, back when the first computer was built over a half century ago, it filled an entire room with circuitry (yet was about as powerful as one of today’s digital wristwatches). One day, the thing was misbehaving, and some brave engineers climbed deep into the thing. (The version we’re thinking of has them wearing white radiation suits, of course.) Deep in The Bowels of the Machine (sounds like a movie title), they found none other than . . . an insect! A bug! It was a great big bug that had gotten messed up in the circuitry, causing the computer to malfunction. So the story goes, anyway. Today, we use the term bug to mean something that is wrong with a program. In this minibook, we show you how to track down bugs and fix them in your software. In this chapter, we talk about what exactly a bug is (and is not!), how bugs occur, and how you can try to avoid them.

It’s Not a Bug. It’s a Feature!

So we’re using Microsoft Word, and all of a sudden, the program freaks out and saves our file automatically. We didn’t tell it to do that; we didn’t ask for it. Then we’re using the same copy of Word, and we try to do a copy-and-paste procedure (that’s called a use case, by the way), and suddenly the Font dialog box pops up. And then later, we’re sitting with our laptops at Starbucks, and it automatically begins the shutdown procedure. We didn’t tell it to do that.

Bugs! Bugs! They’re all bugs! Or are they? Seems that these pesky little incidents might be considered features by some programmers.

Turns out that Microsoft Word has an optional autosave feature that causes it to automatically save recovery information in case our computer goes dead. And that Font dialog box that popped up was a mistake of ours: we meant to press Ctrl+V, but our fingers slipped and caught the Dkey instead. As it happens, by default Ctrl+D opens the Font dialog box in Microsoft Word. (Why D? We have no idea.) And newer versions of Windows understand laptop computers: When the battery is just about to be completely drained, Windows saves the entire state of your machine to a giant file on the hard drive and shuts down. This is called hibernation. So these aren’t bugs after all. We guess we can close up that bug report we just sent to Microsoft.

Now consider this: Suppose you’re using some program that we won’t name here, and in the middle of it, you get a message box that says something like Exception Error, and the program just closes. All your work was lost. So you call tech support, and the helpful friend on the other end says, “You must have typed something it didn’t like. This program has a built-in protection scheme whereby if you type something you’re not supposed to, it shuts down.”

Oh, yeah. We get it. That’s when the guy says, “It’s a feature, not a bug!” Tell me another one. But sometimes situations walk the fine line between bug and feature. We don’t think that a program crashing could be considered a feature, but consider this instead: When Microsoft Internet Explorer 6.0 messes up, a message asks if you want it to send Microsoft a trouble report. That’s a feature that handles bugs.

But the unnamed program that shut down definitely has a bug. And other programs have bugs. For example (we can pick on Internet Explorer, right?), we have been quickly switching between Internet Explorer windows, typing, resizing, doing things quickly as we go back and forth between the windows (too much caffeine perhaps), when suddenly the thing crashes, and we get the trouble-report message. That really was a bug: The program choked when we, the user, did something that the programmers did not anticipate.

Now why did the program choke? Well, in addition to what we did that the programmers hadn’t expected, it’s possible that the programmers simply messed up. Either they didn’t include code to handle a rough situation (rapidly switching, resizing, that sort of thing), or perhaps they wrote code that did something wrong, such as freed a pointer but then continued to use the memory address.

Here’s an example of programmers not expecting something. Suppose we’re writing a program that reads a number from the console. You should type a single character for your first choice and then another character for your second choice. The code might look like this:

char x, y;

cout << “Enter your first choice” << endl;

cin >> x;

cout << “Enter your second choice” << endl;

cout << x << endl;

cin >> y;

cout << y << endl;

A simple little code, but suppose that you respond to the first request by typing an entire word for what you want, such as Read rather than a single letter such as R. Our program would then take the letters e, a, and d and use those for the subsequent cin calls, something we might not have anticipated. The e would go into the cin > y line and get put in y. That’s the bug of not anticipating something: You, the programmer, must make sure that your program can handle all situations. All of them. Every single one. But fortunately, there are ways around such problems, and we share these with you in this chapter.

You can group these situations into the following categories:

♦ Real features, not bugs at all

♦ A situation that the programmers didn’t anticipate

♦ A mistake, plain and simple

Make Your Programming Features Look Like Features

The last thing you want is to get calls from users complaining about a bug in your program that was, in fact, a feature. This can happen, and it does. But the technical support people are embarrassed when they have to explain, “No, sir/ma’am. That really is the way it’s supposed to work.” And it’s also not fun for the technical support people to get called mean names after this, especially when they didn’t write the software — you did.

But as programmers, we want to make everybody’s lives easier (starting with our own, of course!), so building our software so that it’s easy to use and makes sense is best. The key, then, in creating software where the features actually look like features is to make it all sensible. Don’t have your software start the Zapper automatically unless the user explicitly asks that the Zapper come on:

Smiling technical support representative: “It’s a feature! The Zapper comes on after the computer has been sitting idle for ten minutes.”

Angry customer: “Yes, but I would kind of like to be at least ten feet away from the thing when the Zapper starts up!”

Smiling technical support representative: “But why would you be sitting there for ten minutes not using the computer if you’re not away from it?”

Angry customer: “I was reading the manual on how to configure the Zapper!”

You know the rest: Lawsuits follow and people get fired. Not a pretty sight, and that says nothing for the poor customer who was in the vicinity of the computer when the Zapper kicked in at full force.

image With features, the rules are simple: Let the user choose which features they want to happen when. If they don’t want autosave, then let them turn it off. Let them configure the software, and don’t let it do anything surprising.

Anticipating (Almost) Everything

When you write a program, try to anticipate the different things that users can do to your program — much of which may not exactly be associated with the proper use of your program. Most of this kind of protection — that is, ensuring that your program doesn’t choke when the users do something you don’t anticipate — you build into your software centers around the user interface, the area where the users interact with your program.

If your program is a console-based application or if users can enter things into text boxes in a windowing program, you must guard against invalid input. Take a look at this output from a hypothetical program:

What would you like to do?

A. Add random information to the system

B. Boil information

C. Compress information

D. Delete the information

Your choice:

Now suppose that the user chose D for Delete, and the following menu appeared:

What would you like to delete?

A. None of the data — forget it!

B. Some of the data.

C. Most of the data.

D. All the data! Get rid of it all!

Now imagine that a user just starts this program and sees the first menu. The user doesn’t know whether to type A for the first choice or Add for the first choice. The user types Add and presses Enter. Oops. The A went to the first choice, and the system added the random information and printed the same first menu again. The d (the second character the user typed) then went to choice Delete the information. That caused the second menu, the Delete menu to appear. The third character the user typed, d, caused the second menu’s D selection to take place, All the data! Get rid of it all!, all in one shot, without the user realizing what happened.

Oops! What was supposed to be Add turned into Add, Delete, Delete all the data. Not good! How can you avoid this kind of thing?

♦ Restrict the user’s choices.

♦ Clearly state what the user should do.

♦ Support multiple options.

♦ Anticipate what could go wrong.

For example, you might tell the user to type only a single character, with a message such as this:

Please enter a single character for your choice:

But now, does the user have to press Enter afterwards? This message suggests so. But maybe not. So you must be more specific. Maybe one of these would be better:

Type a single character and do not press Enter:

Type a single character and then press Enter:

But even these aren’t good enough. First, you should generally allow the user to press Enter. Doing something automatically with a single keystroke may surprise the user. Further, you may want to support multiple options. If the user wants to choose option A in the menu, then you might support any of the following for input:

♦ A

♦ a

♦ Add

♦ ADD

♦ add

This can all be wrapped up into some short code that looks like this:

string choice;

cin >> choice;

char ch = choice[0];

ch = toupper(ch);

switch (ch)

{

case ‘A’:

cout << “Adding random data...” << endl;

break;

case ‘B’:

cout << “Boiling it down!” << endl;

break;

case ‘C’:

cout << “Compressing!” << endl;

break;

case ‘D’:

cout << “Deleting...” << endl;

break;

}

Now the user can type any word, and the only thing that the program checks is the first letter. But if you don’t like the idea that aompress can be taken as add and not compress (who knows what they meant to type?), you can do something like this:

string choice;

cin >> choice;

choice = MyUppercase(choice);

if (choice == “A” || choice == “ADD”)

{

cout << “Adding random data...” << endl;

}

else if (choice == “B” || choice == “BOIL”)

{

cout << “Boiling it down!” << endl;

}

else if (choice == “C” || choice == “COMPRESS”)

{

cout << “Compressing!” << endl;

}

else if (choice == “D” || choice == “DELETE”)

{

cout << “Deleting...” << endl;

}

else

{

cout << “I don’t know that word” << endl;

}

Now this code looks for only the first letter, or the exact word, and the letter or word can be in either uppercase or lowercase. This choice is probably the best one. However, you may notice that we used a function called MyUppercase. That’s our own function because support in C++ for converting an entire string to uppercase leaves a bit to be desired. So we wrote our own function. Here it is:

string MyUppercase(string str)

{

char *buf = new char[str.length() + 1];

strcpy(buf, str.c_str());

strupr(buf);

return string(buf);

}

But be careful if you’re dealing with a sophisticated program. Suppose you are writing a program that looks up information in a database for a particular customer name. You could run into the following situations:

♦ The names in the database are all in uppercase (for example, GEORGE WASHINGTON), and the user can enter names in mixed case (for example, George Washington).

♦ The first and last names are stored separately, so your program must look in the database for the situation where the last name is Washington and the first name is George. The user doesn’t know to enter just the last name, and may enter both names into a single text box. Or you might allow the user to enter both names at once, but the user didn’t know he or she was supposed to put last name first, or perhaps last name, then a comma, then the first name.

♦ The user can type some spaces at the beginning or end of the name. The program will then look for George Washington and not find it because it’s stored as George Washington (with no spaces before or aft).

♦ The user might include middle initials when the name is not stored in the database with middle initials.

All these problems are easy to avoid. Here are some tips:

♦ You must know how the names are stored in the database before you go looking for them. If they are stored in all caps, you shouldn’t require the user to enter them in all caps. Instead, accept words in any case and convert them to all uppercase.

♦ You must know if the names are stored with first name separate from last. Then allow any format. If the user types George Washington (no comma), you can split the string at the space and pull out the first and last name. But if the user types the name with a comma between the first and last name, you can split it at the comma and extract the last name and then the first.

♦ Spaces should not be a problem. You can strip the spaces off a string after a user types it in.

♦ Are middle initials required? Document things well. Your program should clearly tell the user whether to enter a middle name, a middle initial, or neither. If you are using text controls, don’t even include a middle name field if you don’t want a middle name. Or if you do, specify right on the window whether the user should type a middle initial or an entire middle name. If the entry is just an initial, you can remove a trailing period, or add it, depending on what’s stored in the database.

All these steps will help make your program bulletproof. The idea is to encourage the users to do things the way they prefer, but to prevent them from doing things in ways that your program doesn’t like. If your program doesn’t want middle initials, don’t give the users the opportunity to enter them.

Listing 1-1 shows you how you can strip spaces, strip a possible period off the end of a middle initial, and split a string based on either spaces or commas. In this listing, we used a special class called vector. The vector class is much like an array, except that the vector class is a bit more powerful: vector is a class, and you can add things to it and remove things from it easily by using member functions. vector is also a template, so when you declare it, you must state what type of variables you want it to hold. You put the type of variables in angled brackets. We’re putting strings in it, so we declared it as vector<string>. But to make our lives simpler, we used a typedef to make an easier name for this type: StringList.

The myth of the bulletproof application

Anyone who has spent time reviewing the trade press knows that Internet Explorer and many other applications seem to have a recurring problem with bugs. Just as soon as the vendor fixes one bug, another bug turns up. Some developers may think that the developers at these companies are morons and are giving us all a black eye. However, these developers, more often than not, are just like us. Because they’re human and humans make mistakes — both at the developer and user end of the application — applications will never become bug free. Sure, you may be able to create a nearly bulletproof simple application, but as application complexity increases, so do the number of interactions and the number of potential bugs. At some point, the number of interactions between application parts increases to the point that a bug-free application becomes impossible.

Over the years we have read any number of articles and books that purport to give you the magic required to create an application that not only lacks bugs but also prevents unanticipated user actions. The bulletproof application is a myth. If you buy into this myth, you may be tempted to stop looking for bugs the moment the development staff can’t find anymore. Unfortunately, this attitude leads to headlines proclaiming your application as the next significant Windows security hole. Don’t buy into the bulletproof application myth — always be alert for potential errors.

Listing 1-1: Processing Strings to Reduce Bugs

#include <iostream>

#include <vector>

using namespace std;

typedef vector<string> StringList;

StringList Split(string orig, string delims)

{

StringList list;

int pos;

while((pos = orig.find_first_of(delims)) != -1)

{

list.push_back(orig.substr(0, pos));

orig = orig.substr(pos + 1);

}

list.push_back(orig);

return list;

}

string MyUppercase(string str)

{

char *buf = new char[str.length() + 1];

strcpy(buf, str.c_str());

strupr(buf);

return string(buf);

}

string stripspaces(string orig)

{

int left;

int right;

// If string is empty, just return it.

if (orig.length() == 0)

return orig;

// Strip right

right = orig.find_last_not_of(“ \t”);

if (right > -1)

orig.resize(right + 1);

// Strip left

left = orig.find_first_not_of(“ \t”);

if (left > -1)

orig.erase(0, left);

// If left still has a space, it

// means the whole string is whitespace.

// So just remove it all.

if (orig[0] == ‘ ‘ || orig[0] == ‘\t’)

{

orig = “”;

}

return orig;

}

void ProcessName(string name)

{

StringList list;

string first, middle, last;

int size, commapos;

name = stripspaces(name);

commapos = name.find(“,”);

if (commapos > 0)

{

// Name has a comma, so start with last name.

name.erase(commapos, 1);

list = Split(name, “ “);

size = list.size();

if (size > 0)

last = list[0];

if (size > 1)

first = list[1];

if (size > 2)

middle = list[2];

}

else

{

// Name has no comma, so start with first name.

list = Split(name, “ “);

size = list.size();

if (size > 0)

first = list[0];

if (size > 2)

{

middle = list[1];

last = list[2];

}

if (size == 2)

{

last = list[1];

}

}

// If middle name is just initial and period,

// then remove the initial.

if (middle.length() == 2)

{

if (middle[1] == ‘.’)

{

middle.erase(1,1);

}

}

// Convert all to uppercase

first = MyUppercase(first);

middle = MyUppercase(middle);

last = MyUppercase(last);

cout << “first: “ << first << endl;

cout << “middle: “ << middle << endl;

cout << “last: “ << last << endl;

cout << endl;

}

int main()

{

string name;

name = “ Washington, George Zeus “;

ProcessName(name);

name = “Washington, George Z.”;

ProcessName(name);

name = “George Z. Washington”;

ProcessName(name);

name = “George Zeus Washington”;

ProcessName(name);

name = “George Washington”;

ProcessName(name);

return 0;

}

Listing 1-1 is rather bugproof, but it still won’t handle some situations properly. For example, if somebody tries to process a string with a middle name such as Zeus. (notice a period is present after the name), the program won’t remove the period. But is that correct or not? Who knows, but people don’t normally format names like that. And so here are some improvements you might make to this program:

Eliminate improper characters: You could make sure that no improper characters appear in the names. We would probably do this after we found the first, middle, and last names; that way, we wouldn’t kill the attempt to find the data based on the presence of a single comma that might be needed to specify the name order. You can use various if statements to do this kind of thing.

Handle more names than three: We would probably have a special precaution for the case of more than three names. Some people have lots of names — like 10 or 11 — especially if they’re British royalty. But if this program is to be used, for example, in an oil change operation, we don’t think you’ll see Charles Philip Arthur George, Prince of Wales coming through (although it’s possible). And so, as usual, how you handle the names depends on your particular situation.

Perform initial processing: We would also do some initial processing. Right after the user enters the names, we would make sure that the names are not empty strings, that is, “” (one pair of quotation marks with no space between them).

Avoiding Mistakes, Plain and Simple

Even though many programmers take measures to prevent bugs, programmers still sometimes let problems slip through. However, if you’re careful, you can avoid a lot of these problems. When you create software, you should be in the right frame of mind to watch for potential problemsas you’re writing the code. (Getting into the right frame of mind includes ensuring you have enough sleep, avoiding distractions, and doing other things that help you concentrate on your work, but this section also describes some specifics you should consider.)

The list of potential problems that we’re giving you here could probably go on and on for thousands of pages. However, the point is not to have a big checklist but rather for you to go through this list and start to recognize the things you need to do to write good code. Writing code is conscious and deliberate. It’s like when you are going down a sidewalk and are vaguely aware of such things as whether cars are coming or whether you need to step over any holes. These hazards are always in the back of your mind as you carefully walk along. Writing code is the same way: Certain gotchas should stay in the back of your mind, too:

Indexes: Strings and arrays run from index 0 to 1 less than the length. Using a loop, such as for (i=0; i<=size; i++), is a common mistake. The less-than-or-equal-to symbol is incorrect, yet people make this mistake a lot. The scary thing is that sometimes the code will still function, and you’ll just end up overwriting something else. And worse, you might not immediately catch this coding error, so it will manifest itself as a bug in the program later.

For every new, there’s a delete: Whenever you allocate an object using new, remember to free it. But forgetting the delete doesn’t usually create noticeable bugs in your program; read the next item to see what’s more likely to cause a noticeable bug.

Remember what you deleted: Worse than forgetting to delete an object is forgetting that you deleted it and continuing to use it. When you delete a pointer, make sure that you didn’t pass it to some other object that stored it away and plans to use it again.

Don’t forget to create an object: You may have seen this one: An error message pops up that says:

The instruction at 0x00402119 referenced memory at 0x00000000. The memory could not

be written.

This means someone had a pointer variable and forgot to call new. We generated this message easily on purpose with the following code:

int *x = 0;

*x = 10;

We created a pointer variable and initialized it to 0, meaning it’s not being used. But before calling new or setting the variable equal to some object’s address, we tried to stuff something into the memory it points to (which is address 0, something the operating system doesn’t like). And the operating system responded with the error message. This is a bug we see far more than we would expect in commercial software.

These are just a few items to think about, but you can see that they mostly deal with memory issues, such as allocating memory and using it incorrectly. Most important, you can avoid them if you’re conscientious with your programming. As you code, bear in mind the repercussions of what you’re doing. And as crazy as this sounds, remember what you might be forgetting! Ask yourself, am I forgetting to delete some pointers or that someone else has a copy of this pointer I’m about to delete? If you keep these things in mind, you should avoid some of the most common bugs.