(c) 1995 andrew thomas-cramer
This document may be folded, spindled, or mutilated as desired, as
long as the copyright notice and this restriction remain untouched.
[From the UNIVERSITY OF WISCONSIN-MADISON's Data-Structures Course (CS367) Homepage.]
Some problems can be prevented before they arise. Debugging is a curative approach; defensive programming is a holistic, preventative approach.
- assert( condition ); HIGHLY RECOMMENDED FOR CS367.
- This preprocessor
statement can save you much time while you're testing the program.
If the condition evaluates to true, execution continues normally.
If the condition ever evaluates to false, the
assertmacro will print tostderra message, such as:Assertion failed: condition, file filename, line linenumThen execution stops immediatately.
Typically,
asserts are placed at the beginnings of functions, testing that their arguments satisfy limitations assumed:#include ... real fun( Link *plink, int val ) { assert( pLink != NULL && val >= 0 ); With the inclusion of the
assert, if the assumptions are violated, the program announces this, rather than skipping merrily along and generating unexpected behavior. Theassertgives you a starting point: you know your assumptions were violated before the function was called. You can backtrack, perhaps with a debugger, to find the source of the error.Asserts don't slow down the released software at all, by the way, if they're turned off. Defining the macroNDEBUGturns offasserts. This can be done easily as a compiler flag:g++ -DNDEBUG train.C This means that you should not hesitate to use
asserts for concerns of efficiency, and also that they are effective during testing, but not after release of the software. - default: or else
- Even if the
switchorif-else-ifselection construct has no default case, it may be useful to include it anyway, to announce an error if an "impossible" case has occurred.switch( ws ) { case '\t': ... break; case ' ': ... break; default: cerr << "ERROR! Unexpected character in ws switch! Run for the hills!" << endl; break; } If you like, you can bracket this error-checking default case in#ifndef NDEBUGand#endif, so that it may be turned of in the release version similarly toasserts. - Documentation
- Write documentation while developing your code. This improves the
readibility of your program and clarifies your intent both during and
after development.
- #ifndef THISFILE_H
#define THISFILE_H 1
...
#endif - HIGHLY RECOMMENDED FOR CS367. Many header files cause compiler errors if they're
#included more than once in the same source file. But it's common in larger programs for a header file to appear in more than one place in the#includetree -- it may be included by each of two other header files, or included by both a header file and the source file. The code above, bracketing the contents of a header file calledthisfile.h, tells the preprocessor to#includethe file only once, regardless how many times it's requested.Including the dummy
1after THISFILE_H should not be necessary, but some compilers have difficulty with macros defined to be white space.Some compilers have special means to accomplish the same effect; e.g., flags and
#pragmas. Such are nonstandard, however; the code above is portable to any compiler. - _new_handler
- Your program should behave gracefully if an attempt to allocate memory
fails. This can require you to include an
ifstatement after every use ofnew:Link *p; ... p = new Link; if ( ! p ) { cerr << "Memory allocation failed." << endl; exit(-1); } If you have many allocation statements, this quickly becomes tiresome. An alternative allows you to usenewwithout explicit tests.If
newfails, it checks a global variable_new_handler. If that variable has the value 0, as is the default, thennewjust returns 0. If that variable has the address of a function, however,newcalls that function. Typically, the function prints an error message and performs any last-minute cleanup, such as saving files, before the program quits.The following code sets the new handler in GNU g++:
#include void StandardNewHandler( ); int main() { set_new_handler( StandardNewHandler ); ... } void StandardNewHandler( ) { cerr << "Memory allocation failed." << endl; } The
set_new_handler()function is nonstandard. In other compilers you need to assign directly to the_new_handlervariable. For example, in Symantec C++:#include extern void (*_new_handler)(); // ( _new_handler is a pointer to a void // function which accepts no arguments ) int main() { _new_handler = StandardNewHandler; ... } void StandardNewHandler( ) { cerr << "Memory allocation failed." << endl; } Note that you can make your program easily portable between two compilers through the use of
-Dmacroin your Makefile, and#ifdef-#elif defined()-#endifin your code. - Style
- Use a consistent, readible style in your programming.
Jan. 20, 1995 Andy Thomas-Cramer andrewt@cs.wisc.edu