Path: utzoo!utgpu!news-server.csri.toronto.edu!mailrus!uunet!cs.utexas.edu!yale!cmcl2!lanl!u096000 From: u096000@lanl.gov (Roger A. Cole) Newsgroups: comp.lang.c Subject: assert(): a "tutorial" Keywords: assert() Message-ID: <48462@lanl.gov> Date: 12 Apr 90 15:38:36 GMT Organization: Los Alamos National Laboratory Lines: 147 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 ****************************************************************************** My thanks to the following for their helpful input: bobtl%toolbox.wv.tek.com@RELAY.CS.NET D. Richard Hipp Peter Montgomery john@jupiter.nmt.edu (John Shipman) madd@world.std.com (jim frost) joshua@Atherton.COM (Flame Bait) cox@stpstn.UUCP (Brad Cox) ****************************************************************************** Roger Cole, Los Alamos National Laboratory Internet: cole@luke.lanl.gov