Debugging


C++ and C Debugging, Testing,and Reliability

reviewed by Andrew Phillips


Title: C++ and C Debugging, Testing, and Reliability
Author: David A. Spuler
Publisher: Prentice Hall, 1994
Pages: 338
Price: $36.00
ISBN: 013-308172-9

Books on C and C++ tend to fall into two categories. There are those that are difficult to understand because the writing style is terse and the concepts are insufficiently explained -- but the author usually has a comprehensive understanding of the language and makes few mistakes.

At the other extreme are many C programming books around that are easy to read but lacking in other areas. The authors of these books often do not know a lot about C, only a particular implementation of it. When you learn from these books you are not learning C, but only something similar to it. I am amazed how often I see:


printf("%lf", x);

and similar statements with undefined behavior printed in some of these books.

David A. Spuler's books are an exception. They are easy to read but have very few errors and implementation dependencies. For example, Comprehensive C is the best book I have read on C, being comprehensive and yet lucid. So I was delighted to come across his new book, C++ and C Debugging, Testing and Reliability, which covers an area of C programming in which I have a great interest.

This book gives a very thorough analysis of how to handle errors during the creation and execution of software. The book is aimed at the intermediate to advanced C and/or C++ programmer. It explains how to detect and fix bugs, and how to avoid creating them altogether. It also covers strategies for handling internal and external errors at run time.

The text is liberally sprinkled with interesting example code. These are not in the form of pages and pages of overwhelming listings; each example is smaller than a page. A companion diskette includes the source code for all examples longer than a few lines, as well as other examples which were apparently too large to be printed. The diskette also contains the source for several useful debugging utilities. (It would have been even nicer if all the freeware tools that were mentioned had been included.)

C++ Coverage

The title of the book and the fact that Spuler has previously written only C books might indicate that (like some other recent books) this is really a C book with some C++ pieces tacked on. This is not the case. Throughout the book, Spuler mentions C++ specific features and peculiarities whenever relevant. There are several chapters dedicated to C++ specific topics. I found the chapter on C++ exception handling particularly accurate and easy to understand. Spuler's knowledge of C++ is as complete as his knowledge of C.

Nevertheless, a large portion of the book will not be relevant to C++ programmers. (Conversely, much is not relevant to straight C programmers.) For example, C++ programmers won't care about problems with passing parameters to printf; they have the iostreams library and its greater type safety. This suggests something which would make a useful addition to the book, particularly for a C programmer moving to C++: better highlighting of how certain traditional C problems are avoided in C++.

Topics Covered

The book is divided into two sections: "Techniques and Tools," and "Catalog of Common Errors." The first section contains chapters on techniques for testing, debugging, handling exceptions, and making code more reliable. The second section provides extensive coverage of bugs that are common in C and C++ -- how they occur and how to avoid them.

The book also includes a chapter called "Software Quality Assurance," which I did not find particularly useful, perhaps because this is outside Spuler's area of expertise. He seems to equate SQA with certification to a quality standard (in particular ISO-9001). This is typified by his statement "QA is not the easiest path to quality software." This should read "certification is not the easiest path to quality software." In my opinion, a fundamental objective of SQA is finding easier paths to quality software (among them avoiding many bureaucratic procedures typical of quality standards).

Interestingly, there is a chapter containing product reviews of many debugging and testing tools (both commercial and freeware), which is a little unconventional for this type of book. Presumably Spuler will update this chapter in later editions as the products are improved and new ones come on the market. Although the chapter did not cover some products that I am interested in, it was still useful to know what type of products are available.

I believe the chapter on product reviews should be prefaced with a warning. In my experience making these sorts of tools available can actually lead to more bugs and problems in the final program than not using them. Programmers may suffer from "automation complacency," that is, relying on the tools to find their bugs and becoming less stringent in their own checking and testing. These sorts of tools can only hope to turn up a fraction of existing bugs and may consequently be counter-productive.

I did note one mistake in this book, in its explanation of signal handlers. Spuler does not distinguish between synchronous signals (such as SIGFPE) and asynchronous signals (such as SIGTERM). For example, he says not to use longjmp within a signal. Of course, you should not call longjmp (or any standard library function except signal) within an asynchronous signal handler, but it's okay in a synchronous signal handler.

Necessarily, in a book of this nature, much of the discussion focuses on things outside the bounds of Standard C, for example the notorious undefined and implementation-defined behaviors. It makes sense to include this sort of information in a debugging book; knowing the likely behavior of a program can facilitate debugging, even if the C standard insists that anything may happen (presumably, as long as it does not contravene the laws of mathematics and physics). In this respect Spuler does fairly well, but inevitably betrays his OS preferences. The symptoms he gives for undefined behavior are often UNIX symptoms. For example, the statement that "dereferencing a null pointer causes an immediate crash" (page 285) may be true on his system but not in general -- the C Standard only says that the behavior is undefined.

In fact, Spuler's discussion on use of errant pointers is completely inadequate for programmers dealing with certain primitive operating systems. On these systems a program can, for example, overwrite its own code. The book should warn of this possibility. Ideally, a debugging book would present techniques to deal with such problems, for example, by regularly performing a CRC on the code segment to determine when and where the problem occurs.

Despite his affinity for UNIX, Spuler does condescend to discussing those odious near/far pointers which many of us have to deal with.

A Catalog of Errors

The book's catalog of errors would be very useful to any programmer interested in ensuring that his code has as few bugs as possible. However, as the book is aimed at intermediate to advanced programmers, I feel Spuler should have omitted a few of the simpler examples to make way for some that were missed.

Some topics do not receive adequate coverage, given their intended audience. For example, in his discussion of side effects and sequence points Spuler forgot to include the sequence point that occurs when a function is called (after evaluation of the arguments and function call designator). Spuler's (overly) simple rule for avoiding undefined behavior from evaluation order dependencies is "if a variable has a side effect in a statement, ensure the variable occurs only once in that statement." This rule would preclude obviously correct statements such as x = -x; and y = 1/y.

Another problem crops up in Spuler's discussion of realloc. He points out (correctly) that the standard library function realloc may move the block of memory and that the following:


/* WRONG */
realloc(ptr, BIGGER_SIZE);

is bad news, if the block has to be moved. However, his alternative,


ptr = realloc(ptr, BIGGER_SIZE);

exhibits another problem. A memory leakage will occur if a block of the larger size cannot be allocated. The correct thing to do is as follows:


tmp = realloc(ptr, BIGGER_SIZE);
if (tmp != NULL)
    ptr = tmp;
else
    /* Handle resize failure, if necessary */

Spuler also correctly says that using the "%lf" format specifier is incorrect for a double, but then goes on to say that "%lf" is instead intended for a long double. In fact, using "%lf" results in undefined behavior whether or not the corresponding parameter is double or long double. Instead "%Lf" (with a capital L) should be used for long double (and plain "%f" for double).

Of course this mistake is far less serious than that of many authors who freely use "%lf". These authors betray their lack of knowledge of the C standard and lack of experience of non-MS-DOS environments. (Most MS-DOS compilers happily accept "%lf".)

Finally, "Appendix A - Symptom Based Error Diagnosis" makes an interesting attempt to reduce debugging effort. Given the manifestation of a bug, Spuler lists possible causes. This approach will be of some use to inexperienced programmers, but the worst types of bugs (for example, uninitialized pointers) often exhibit behaviors that are not readily classifiable.

One nit-pick: Spuler occasionally refers to ANSI C and ANSI C++. I found this annoying as ANSI C has been adopted as an international standard (ISO C), and there is no such thing (yet) as ANSI C++. I would have preferred that he use the names "Standard C" (or even C-90) and "draft Standard C++."

Summary

In summary, this book is an excellent attempt at addressing an increasingly important area of C and C++ programming -- creating reliable software. And this an area that does not receive adequate coverage from currently available books.

However, this book could stand several improvements to its overall coverage as well. For the intended audience many subjects, such as sequence points, errant pointers etc., do not receive adequate coverage. (On the other hand, other topics, such as using debugging macros, are covered in excruciating detail.) I would also like to see more attention given to OS-specific debugging techniques (apart from UNIX).

I found the catalogue of errors to be the highlight. Those programmers reasonably familiar with C and/or C++ will find a wealth of ideas to enable them to write better code. Even very experienced programmers will find many useful ideas.

Andrew Phillips is the Senior Software Engineer with Encom Technology. He has a BSc in computer science from Sydney University, and has been programming in C and C++ since 1984. His interests include software quality assurance and enhancing the reliability of C and C++ code. Andrew can be reached at andrew@encom.com.au.