Debugging

 
          ************************************************
         *  An essay on assert, notes on XDB, GCC, SYSr4  *
         *                and floats                      *  
          ************************************************


              ******** ASSERT *********



This article presents some information about using assert() in developing
C software.  This information is based in part on responses from
comp.software-eng (see end of article for credits).  ** The perspective
presented below is one of improving code quality, not of program proving. **

assert(expr) is a macro which checks the 'expr'.  If 'expr' is false, an
error message is printed and 'evasive action' (often just exit(1); ) is taken.
The assert() macro typically is disabled if the preprocessor symbol NDEBUG
is defined.

Guidelines for using assert() include:
1.  assert()'s should be enabled only in development code, and never in
    production versions.
2.  Do not use assert() for checking user input or other 'unpredictable'
    data.  Use other checking mechanisms for these types of conditions.
3.  Use assert() for checking conditions which, if violated, would result
    in program malfunctions.

Valuable aspects of using assert() include:
1.  assert() provides a compact method for checking important conditions at
    run time.
2.  assert()'s are not present in production code, so they don't affect size
    and speed of production code.
3.  Since the assert()'s aren't actually removed from the source code, they
    a.  provide a method for trouble-shooting when a problem occurs; and
    b.  provide 'documentation' within the code of important conditions.

Weaknesses of using assert() include:
1.  Some conditions are difficult to check.  For example, the desirable check
    for a pointer argument would be "it's a valid pointer"; generally, a
    subset of this condition is checked--"it's not a NULL pointer".
2.  assert() operates at run time, and thus has limited capability for doing
    compile time checks.
3.  assert() can't be used to actually validate most algorithms--just
    conditions during the execution of an algorithm.
******************************************************************************
A simple example.  Assume 'pStruct' points to a structure containing several
items, with items tied in some way to a number.  Based on a user-supplied
item number, obtain in 'pItem' a pointer to the corresponding item.  If the
user supplies an invalid item number, keep retrying until a valid item number
is obtained; if an invalid item number is supplied to the look-up routine,
abort.  (This example is focused on using assert(), not on "proper" C--no
flames please :-) .)

    while (menuItem < 0 && menuItem > maxItem) {
       code to get a menu item number from a user;
       if (menuItem < 0 || menuItem > maxItem)  /* don't use assert() here */
	   printf("illegal item number\n"
    }
    pItem = lookUp(pStruct, menuItem);


ITEM *
lookUp(pStruct, itemNum)
STRUCT *pStruct;
int	itemNum;
{
    ITEM *pItem;

    assert(pStruct != NULL);
    assert(itemNum >= 0);
    assert(itemNum <= maxItem);

    code to get address of desired item;

    assert(pItem != NULL);
    return pItem;
}
******************************************************************************
My goals for implementing assert():
1.  The default method of 'make'ing the code for production must disable the
    checks.
2.  Even during development, minimize the impact on program size of using
    assert().
3.  Allow easily setting breakpoints to catch assertion failures.
4.  Impose as few restrictions as possible on the use of assert().

With these goals in mind, I have settled on the following form for assert(),
which I'm using with the Sun3 C compiler.  There is a xxx.h part and a
xxx.c part.

#ifdef DEBUG
/*----------------------------------------------------------------------------
* assert()
*
* DESCRIPTION
*	assert() evaulates an expression.  If the expression is non-zero
*	(i.e., "true"), then no action is taken.  If the expression is zero,
*	then the file name and line number are printed to
*	stderr and an exit(1); is done.  If a #define DEBUG hasn't been
*	done, then assert() does nothing.
*
* EXAMPLES
*	assert(pBuf != NULL);
*	assert(strlen(name) < 20), printf("%s\n", name);
*
* NOTES
* 1.	this uses a C 'trick'--if 'expr' is TRUE, then C doesn't execute what
*	follows the || .
* 2.	this macro definition comes from "C Traps and Pitfalls", Andrew
*	Koenig, Addison-Wesley, 1989, page 82.
*---------------------------------------------------------------------------*/
#   define assert(expr) \
		((void)((expr) || assertFail(__FILE__, __LINE__)))
#else
#    define assert(expr)
#endif



assertFail(fileName, lineNum)
char *fileName;
int lineNum;
{
    (void)fprintf(stderr, "assertFail: in file %s line%d\n",
						fileName, lineNum);
    exit(1);
}
******************************************************************************
Some discussion:

One suggestion was to print the expression which failed.  Although this can be
done in a relatively portable way, for either 'old-timey' or ANSI C
pre-processors, a potential pitfall made me decide not to implement this:  If
'expr' contains a "string", then macro expansion could get confusing.

My normal development and testing scheme uses -DDEBUG, so I generate checks
only if that symbol is defined.  I prefer this to the common strategy of
having using -DNDEBUG to disable generating checks.

Many other uses exist for using assert().  Some possible conditions are:
    "Milestones" during a complicated algorithm
    elapsed time for an algorithm doesn't violate a time constraint

-----------------------------------------------------------------------