Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Professional C++ [eng].pdf
Скачиваний:
284
Добавлен:
16.08.2013
Размер:
11.09 Mб
Скачать

Conquering Debugging

If you don’t have a memory-checking tool at your disposal, and the normal strategies for debugging are not helping, you may need to resort to code inspection. Once you’ve narrowed down the part of the code containing the bug, here are some specific items to look for.

Object and Class-related Errors

Verify that your classes with dynamically allocated memory have destructors that free exactly the memory that’s allocated in the object: no more, and no less.

Ensure that your classes handle copying and assignment correctly with copy constructors and assignment operators, as described in Chapter 9.

Check for suspicious casts. If you are casting a pointer to an object from one type to another, make sure that it’s valid.

General Memory Errors

Make sure that every call to new is matched with exactly one call to delete. Similarly, every call to malloc, alloc, or calloc should be matched with one call to free. And every call to new[] should be matched with one call to delete[]. Although duplicate free calls are generally harmless, they can cause problems if that same memory was handed out in a different memory allocation call after the first free.

Check for buffer overruns. Anytime you iterate over an array or write into or read from a C-style string, verify that you are not accessing memory past the end of the array.

Check for dereferencing invalid pointers.

Debugging Multithreaded Programs

Unlike in Java, the C++ language does not provide any mechanisms for threading and synchronization between threads. However, multithreaded C++ programs are common, so it is important to think about the special issues involved in debugging a multithreaded program. Bugs in multithreaded programs are often caused by variations in timing in the operating system scheduling, and can be difficult to reproduce. Thus, debugging multithreaded programs takes a special set of techniques:

1.Use cout debugging. When debugging multithreaded programs, cout debugging is often more effective than using a debugger. Most debuggers do not handle multiple threads of execution very well, or at least don’t make it easy to debug a multithreaded program. It is difficult to step through your program when you don’t know which thread will run at any given time. Add debug statements to your program before and after critical sections, and before acquiring and after releasing locks. Often by watching this output, you will be able to detect deadlocks and race conditions because you will be able to see that two threads are in a critical section at the same time or that one thread is stuck waiting for a lock.

2.Insert forced sleeps and context switches. If you are having trouble reproducing the problem consistently, or have a hunch about the root cause but want to verify it, you can force certain thread-scheduling behavior by making your threads sleep for specified amounts of time.

Although there is no standard way in C++ to make a thread sleep, most platforms provide a call, often called sleep(). Sleeping for several seconds right before releasing a lock, immediately before signaling a condition variable, or directly before accessing shared data can reveal race conditions that would otherwise go undetected.

547

Chapter 20

Debugging Example: Article Citations

This section presents a buggy program and shows you the steps to take in order to debug it and fix the problem.

Suppose that you’re part of a team writing a Web page that allows users to search for the research articles that cite a particular paper. This type of service is useful for authors who are trying to find work similar to their own. Once they find one paper representing a related work, they can look for every paper that cites that one to find other related work.

In this project, you are responsible for the code that reads the raw citations data from text files. For simplicity, assume that the citation info for each paper is found in its own file. Furthermore, assume that the first line of each file contains the author, title, and publication info for the paper; the second line is always empty; and all subsequent lines contain the citations from the article (one on each line). Here is an example file for one of the most important papers in Computer Science:

Alan Turing,”On Computable Numbers with an Application to the Entscheidungsproblem”,\ Proceedings of the London Mathematical Society, Series 2, Vol.42 (1936 - 37) pages\ 230 to 265.

Godel, “Uber formal unentscheidbare Satze der Principia Mathernatica und verwant der\ Systeme, I”, Monatshefte Math. Phys., 38 (1931). 173-198.

Alonzo Church. “An unsolvable problem of elementary number theory”, American J of\ Math., 58(1936), 345 363.

Alonzo Church. “A note on the Entscheidungsprob1em”, J. of Symbolic logic, 1 (1930),\ 40 41.

Cf. Hobson, “Theory of functions of a real variable (2nd ed., 1921)”, 87, 88.\ Proc. London Math. Soc (2) 42 (1936 7), 230 265.

Note that the \ character is the continuation character to ensure that the computer treats the multiple lines as a single line during processing.

Buggy Implementation of an ArticleCitations Class

You decide to structure your program by writing an ArticleCitations class that reads the file and stores the information. This class stores the article info from the first line in one string, and the citations info in an array of strings. Please note that this design decision is not necessarily the best possible. However, for the purposes of illustrating buggy applications, it’s perfect! The class definition looks like this:

#include <string> using std::string;

class ArticleCitations

{

public:

ArticleCitations(const string& fileName); ~ArticleCitations();

ArticleCitations(const ArticleCitations& src); ArticleCitations& operator=(const ArticleCitations& rhs);

548

Conquering Debugging

string getArticle() const { return mArticle; }

int getNumCitations() const { return mNumCitations; } string getCitation(int i) const { return mCitations[i]; }

protected:

void readFile(const string& fileName);

string mArticle; string* mCitations; int mNumCitations;

};

The implementations of the methods follow. This program is buggy! Don’t use it verbatim or as a model.

#include “ArticleCitations.h” #include <iostream>

#include <fstream> #include <string> #include <stdexcept> using namespace std;

ArticleCitations::ArticleCitations(const string& fileName)

{

// All we have to do is read the file. readFile(fileName);

}

ArticleCitations::ArticleCitations(const ArticleCitations& src)

{

//Copy the article name, author, etc. mArticle = src.mArticle;

//Copy the number of citations. mNumCitations = src.mNumCitations;

//Allocate an array of the correct size. mCitations = new string[mNumCitations];

//Copy each element of the array.

for (int i = 0; i < mNumCitations; i++) { mCitations[i] = src.mCitations[i];

}

}

ArticleCitations& ArticleCitations::operator=(const ArticleCitations& rhs)

{

//Check for self-assignment. if (this == &rhs) {

return (*this);

}

//Free the old memory. delete [] mCitations;

//Copy the article name, author, etc. mArticle = rhs.mArticle;

//Copy the number of citations. mNumCitations = rhs.mNumCitations;

//Allocate a new array of the correct size. mCitations = new string[mNumCitations];

549

Chapter 20

// Copy each citation.

for (int i = 0; i < mNumCitations; i++) { mCitations[i] = rhs.mCitations[i];

}

return (*this);

}

ArticleCitations::~ArticleCitations()

{

delete[] mCitations;

}

void ArticleCitations::readFile(const string& fileName)

{

//Open the file and check for failure. ifstream istr(fileName.c_str());

if (istr.fail()) {

throw invalid_argument(“Unable to open file\n”);

}

//Read the article author, title, etc. line. getline(istr, mArticle);

//Skip the white space before the citations start. istr >> ws;

int count = 0;

//Save the current position so we can return to it. int citationsStart = istr.tellg();

//First count the number of citations.

while (!istr.eof()) { string temp; getline(istr, temp);

// Skip white space before the next entry. istr >> ws;

count++;

}

if (count != 0) {

//Allocate an array of strings to store the citations. mCitations = new string[count];

mNumCitations = count;

//Seek back to the start of the citations. istr.seekg(citationsStart);

//Read each citation and store it in the new array. for (count = 0; count < mNumCitations; count++) {

string temp; getline(istr, temp); mCitations[count] = temp;

}

}

}

Testing the ArticleCitations class

Following the advice of Chapter 19, you decide you unit test your ArticleCitations class before proceeding, though for simplicity in this example, the unit test does not use a test framework. The following

550

Conquering Debugging

program asks the user for a filename, constructs an ArticleCitations class with that filename, and passes the object by value to the processCitations() function, which prints out the info using the public accessor methods on the object.

#include “ArticleCitations.h” #include <iostream>

using namespace std;

void processCitations(ArticleCitations cit);

int main(int argc, char** argv)

{

string fileName;

while (true) {

cout << “Enter a file name (\”STOP\” to stop): “; cin >> fileName;

if (fileName == “STOP”) { break;

}

// Test constructor ArticleCitations cit(fileName); processCitations(cit);

}

return (0);

}

void processCitations(ArticleCitations cit)

{

cout << cit.getArticle() << endl; int num = cit.getNumCitations(); for (int i = 0; i < num; i++) {

cout << cit.getCitation(i) << endl;

}

}

cout Debugging

You decide to test the program on the Alan Turing example (stored in a file called paper1.txt). Here is the output:

Enter a file name (“STOP” to stop): paper1.txt

Alan Turing.”On Computable Numbers with an Application to the Entscheidungsproblem”, Proceedings of the London Mathematical Society, Series 2, Vol.42 (1936 - 37) pages 230 to 265.

Enter a file name (“STOP” to stop): STOP

That doesn’t look right! There are supposed to be five citations printed instead of five blank lines.

551

Chapter 20

For this bug, you decide to try good ole cout debugging. In this case, it makes sense to start by looking at the function that reads the citations from the file. If that doesn’t work right, then obviously the object won’t have the citations. You can modify readFile() as follows:

void ArticleCitations::readFile(const string& fileName)

{

//Open the file and check for failure. ifstream istr(fileName.c_str());

if (istr.fail()) {

throw invalid_argument(“Unable to open file\n”);

}

//Read the article author, title, etc. line. getline(istr, mArticle);

//Skip the white space before the citations start. istr >> ws;

int count = 0;

//Save the current position so we can return to it. int citationsStart = istr.tellg();

//First count the number of citations.

cout << “readFile(): counting number of citations\n”; while (!istr.eof()) {

string temp; getline(istr, temp);

// Skip white space before the next entry. istr >> ws;

cout << “Citation “ << count << “: “ << temp << endl; count++;

}

cout << “Found “ << count << “ citations\n” << endl;

cout << “readFile(): reading citations\n”; if (count != 0) {

//Allocate an array of strings to store the citations. mCitations = new string[count];

mNumCitations = count;

//Seek back to the start of the citations. istr.seekg(citationsStart);

//Read each citation and store it in the new array. for (count = 0; count < mNumCitations; count++) {

string temp; getline(istr, temp); cout << temp << endl; mCitations[count] = temp;

}

}

Running the same test on this program gives this output:

Enter a file name (“STOP” to stop): paper1.txt readFile(): counting number of citations

Citation 0: Godel, “Uber formal unentscheidbare Satze der Principia Mathernatica und verwant der Systeme, I”, Monatshefte Math. Phys., 38 (1931). 173-198.

552

Conquering Debugging

Citation 1: Alonzo Church. “An unsolvable problem of elementary number theory”, American J of Math., 58(1936), 345 363.

Citation 2: Alonzo Church. “A note on the Entscheidungsprob1em”, J. of Symbolic logic, 1 (1930), 40 41.

Citation 3: Cf. Hobson, “Theory of functions of a real variable (2nd ed., 1921)”, 87, 88.

Citation 4: Proc. London Math. Soc (2) 42 (1936 7), 230 265. Found 5 citations

readFile(): reading citations

Alan Turing,”On Computable Numbers with an Application to the Entscheidungsproblem”, Proceedings of the London Mathematical Society, Series 2, Vol.42 (1936 - 37) pages 230 to 265.

Enter a file name (“STOP” to stop):

As you can see from the output, the first time the program reads the citations from the file, in order to count them, they are read correctly. However, the second time, they are not read correctly. Why not? One way to delve deeper into this issue is to add some debugging code to check the state of the file stream after each attempt to read a citation:

void printStreamState(const istream& istr)

{

if (istr.good()) {

cout << “stream state is good\n”;

}

if (istr.bad()) {

cout << “stream state is bad\n”;

}

if (istr.fail()) {

cout << “stream state is fail\n”;

}

if (istr.eof()) {

cout << “stream state is eof\n”;

}

}

void ArticleCitations::readFile(const string& fileName)

{

//Open the file and check for failure. ifstream istr(fileName.c_str());

if (istr.fail()) {

throw invalid_argument(“Unable to open file\n”);

}

//Read the article author, title, etc. line. getline(istr, mArticle);

553

Chapter 20

// Skip the white space before the citations start. istr >> ws;

int count = 0;

//Save the current position so we can return to it. int citationsStart = istr.tellg();

//First count the number of citations.

cout << “readFile(): counting number of citations\n”; while (!istr.eof()) {

string temp; getline(istr, temp);

// Skip white space before the next entry. istr >> ws;

printStreamState(istr);

cout << “Citation “ << count << “: “ << temp << endl; count++;

}

cout << “Found “ << count << “ citations\n” << endl; cout << “readFile(): reading citations\n”;

if (count != 0) {

//Allocate an array of strings to store the citations. mCitations = new string[count];

mNumCitations = count;

//Seek back to the start of the citations. istr.seekg(citationsStart);

//Read each citation and store it in the new array. for (count = 0; count < mNumCitations; count++) {

string temp; getline(istr, temp); printStreamState(istr); cout << temp << endl; mCitations[count] = temp;

}

}

}

When you run your program this time, you find some interesting information:

Enter a file name (“STOP” to stop): paper1.txt readFile(): counting number of citations stream state is good

Citation 0: Godel, “Uber formal unentscheidbare Satze der Principia Mathernatica und verwant der Systeme, I”, Monatshefte Math. Phys., 38 (1931). 173-198. stream state is good

Citation 1: Alonzo Church. “An unsolvable problem of elementary number theory”, American J of Math., 58(1936), 345 363.

stream state is good

Citation 2: Alonzo Church. “A note on the Entscheidungsprob1em”, J. of Symbolic logic, 1 (1930), 40 41.

stream state is good

Citation 3: Cf. Hobson, “Theory of functions of a real variable (2nd ed., 1921)”, 87, 88.

stream state is eof

Citation 4: Proc. London Math. Soc (2) 42 (1936 7), 230 265. Found 5 citations

554

Conquering Debugging

readFile(): reading citations stream state is fail

stream state is eof

stream state is fail

stream state is eof

stream state is fail

stream state is eof

stream state is fail

stream state is eof

stream state is fail

stream state is eof

Alan Turing,”On Computable Numbers with an Application to the Entscheidungsproblem”, Proceedings of the London Mathematical Society, Series 2, Vol.42 (1936 - 37) pages 230 to 265.

Enter a file name (“STOP” to stop):

It looks like the stream state is good until after the final citation is read for the first time. Then, the stream state is eof, because the end-of-file has been reached. That is expected. What is not expected is that the stream state is both fail and eof after all attempts to read the citations a second time. That doesn’t appear to make sense at first: the code uses seekg() to seek back to the beginning of the citations before reading them a second time, so the file shouldn’t still be at the end. However, recall from Chapter 13 that streams maintain their error and eof states until you clear them explicitly. seekg() doesn’t clear the eof state automatically. When in an error or eof state, streams fail to read data correctly, which explains why the stream state is fail also after trying to read the citations a second time. A closer look at your method reveals that it fails to call clear() on the istream after reaching the end of file. If you modify the method by adding a call to clear(), it will read the citations properly.

Here is the corrected readFile() method without the debugging cout statements:

void ArticleCitations::readFile(const string& fileName)

{

// CODE OMMITTED FOR BREVITY

if (count != 0) {

//Allocate an array of strings to store the citations. mCitations = new string[count];

mNumCitations = count;

//Clear the previous eof.

istr.clear();

//Seek back to the start of the citations. istr.seekg(citationsStart);

//Read each citation and store it in the new array. for (count = 0; count < mNumCitations; count++) {

string temp;

555

Chapter 20

getline(istr, temp); mCitations[count] = temp;

}

}

}

Using a Debugger

The following example uses the gdb debugger on the Linux operating system.

Now that your ArticleCitations class seems to work well on one citations file, you decide to blaze ahead and test some special cases, starting with a file with no citations. The file looks like this, and is stored in a file named paper2.txt:

Author with no citations

When you try to run your program on this file, you get the following result:

Enter a file name (“STOP” to stop): paper1.txt

Alan Turing.”On Computable Numbers with an Application to the Entscheidungsproblem”, Proceedings of the London Mathematical Society, Series 2, Vol.42 (1936 - 37) pages 230 to 265.

Godel, “Uber formal unentscheidbare Satze der Principia Mathernatica und verwant der Systeme, I”, Monatshefte Math. Phys., 38 (1931). 173-198.

Alonzo Church. “An unsolvable problem of elementary number theory”, American J of Math., 58(1936), 345 363.

Alonzo Church. “A note on the Entscheidungsprob1em”, J. of Symbolic logic, 1 (1930), 40 41.

Cf. Hobson, “Theory of functions of a real variable (2nd ed., 1921)”, 87, 88. Proc. London Math. Soc (2) 42 (1936 7), 230 265.

Enter a file name (“STOP” to stop): paper2.txt Author with no citations

Segmentation fault

Oops. There must be some sort of memory error. This time you decide to give the debugger a shot. The Gnu DeBugger (gdb) is widely available on Unix platforms, and works quite well. First, you must compile your program with debugging info (-g with g++). After that, you can launch the program under gdb. Here’s an example session using the debugger to root-cause this problem:

>gdb buggyprogram

GNU gdb Red Hat Linux (5.2-2)

Copyright 2002 Free Software Foundation, Inc.

GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type “show copying” to see the conditions.

There is absolutely no warranty for GDB. Type “show warranty” for details. This GDB was configured as “ia64-redhat-linux”...

(gdb) run

Starting program: buggyprogram

Enter a file name (“STOP” to stop): paper1.txt

Alan Turing.”On Computable Numbers with an Application to the Entscheidungsproblem”, Proceedings of the London Mathematical Society, Series 2, Vol.42 (1936 - 37) pages 230 to 265.

556

Conquering Debugging

Godel, “Uber formal unentscheidbare Satze der Principia Mathernatica und verwant der Systeme, I”, Monatshefte Math. Phys., 38 (1931). 173-198.

Alonzo Church. “An unsolvable problem of elementary number theory”, American J of Math., 58(1936), 345 363.

Alonzo Church. “A note on the Entscheidungsprob1em”, J. of Symbolic logic, 1 (1930), 40 41.

Cf. Hobson, “Theory of functions of a real variable (2nd ed., 1921)”, 87, 88. Proc. London Math. Soc (2) 42 (1936 7), 230 265.

Enter a file name (“STOP” to stop): paper2.txt Author with no citations

Program received signal SIGSEGV, Segmentation fault. __libc_free (mem=0x6000000000010320) at malloc.c:3143 3143 malloc.c: No such file or directory.

in malloc.c

Current language: auto; currently c

When the SEGV occurs, the debugger allows you to poke around in the state of program at the time. The bt command shows the current stack trace. You can move up and down the function calls in the stack with up and down.

(gdb) bt

#0 __libc_free (mem=0x6000000000010320) at malloc.c:3143 #1 0x2000000000089010 in __builtin_delete ()

from /usr/lib/libstdc++-libc6.2-2.so.3

#2 0x2000000000089050 in __builtin_vec_delete () from /usr/lib/libstdc++-libc6.2-2.so.3

#3 0x400000000000a820 in ArticleCitations::~ArticleCitations ( this=0x80000fffffffb920, __in_chrg=2) at ArticleCitations.cpp:51

#4 0x4000000000004f40 in main (argc=1, argv=0x80000fffffffb968) at BuggyProgram.cpp:20

One item of interest in this stack trace is that delete calls free(). It’s actually fairly common for new and delete to be implemented in terms of malloc() and free(). More importantly, from this stack trace you can see that there seems to be some sort of problem in the ArticleCitations destructor. The list command shows the code in the current stack frame.

(gdb) up 3

#3 0x400000000000a820 in ArticleCitations::~ArticleCitations ( this=0x80000fffffffb920, __in_chrg=2) at ArticleCitations.cpp:51

51 delete [] mCitations; Current language: auto; currently c++ (gdb) list

46return (*this);

47}

48

49ArticleCitations::~ArticleCitations()

50{

51delete [] mCitations;

52}

53

54 void ArticleCitations::readFile(const string& fileName)

{

557

Chapter 20

The only thing in the destructor is a single delete[] call. In gdb, you can print values available in the current scope with print. In order to root-cause the problem, you can try printing some of the object member variables. Recall that the string type in C++ is really a typedef of the basic_string template instantiated for chars.

(gdb) print mCitations $3 = (

basic_string<char,string_char_traits<char>,__default_alloc_template<true, 0> > *) 0x6000000000010338

Hmm, mCitations looks like a valid pointer (though it’s hard to tell, of course).

(gdb) print mNumCitations $2 = 5

Ah ha! Here’s the problem. This article isn’t supposed to have any citations. Why is mNumCitations set to 5? Take another look at the code in readFile() for the case that there are no citations. In that case, it looks like it never initializes mNumCitations and mCitations! The code is left with whatever junk is in memory already in those locations. In this case, the previous ArticleCitations object had the value 5 in mNumCitations. The second ArticleCitations object must have been placed in the same location in memory and so received that same value. However, the pointer value that it was assigned randomly is certainly not a valid pointer to delete! You need to initialize mCitations and mNumCitations whether or not you actually find any citations in the file. Here is the fixed code:

void ArticleCitations::readFile(const string& fileName)

{

// CODE OMMITTED FOR BREVITY

mCitations = NULL;

mNumCitations = 0; if (count != 0) {

//Allocate an array of strings to store the citations. mCitations = new string[count];

mNumCitations = count;

//Clear the previous eof.

istr.clear();

//Seek back to the start of the citations. istr.seekg(citationsStart);

//Read each citation and store it in the new array. for (count = 0; count < mNumCitations; count++) {

string temp; getline(istr, temp); mCitations[count] = temp;

}

}

}

As this example shows, memory errors don’t always show up right away. It often takes a debugger and some persistence to figure them out.

If you attempt to replicate this debugging session on a different platform, you may find that, due to the vagaries of memory errors, the program crashes in a different place than this example shows.

558