cox@stpstn.UUCP (Brad Cox) (04/09/90)
In article <47859@lanl.gov> u096000@lanl.gov (Roger A. Cole) writes: >Various articles have discussed the value of assert() in producing quality >software. As a long-time programmer with a relatively recent conversion >to C, I am intrigued. I am also, in keeping with observations in various >articles, ignorant about assert(). (So are other programmers at my site.) Although this paper doesn't mention assert() by name, you might start with my paper, now being reviewed, for the Novembmer 1990 issue of IEEE Software Magazine, "Planning the Software Industrial Revolution; The Impact of OO Technologies". One section of this paper, "Technologies: Programming Languages are not sufficient", derives from Stepstone's experiences in building and supporting commercial libraries of reusable object-oriented code libraries. Since a commercial operation is continually faced with extending code that already has large numbers of clients, porting code to new platforms, and repairing defects, we were continually plagued by the problem of verifying that old client interfaces were not being changed while independently verifying that the new changes in fact complied to the new specification. The system that we presently use to do this is entirely based on programmer supplied assert() statements, of two distinct kinds: (1) structural (white box) testing, with assert()s embedded inside the product, and (2) functional (black box) testing, with assert()s contained in independent files that I call Gauges, by analogy with the gauges that played such an important part in Eli Whitney and his successors' vision of interchangeable parts. The article sketches a more ambitious system that deploys a knowledge reprersentation language as a specification/testing language. If you're interested, send me a mailing address and I'll mail a copy. I have no additional written information about how we use asserts() at this point. However I'll be teaching this approach to specification/testing as part of a Objective-C course at the UC Santa Clara Techmart Extension the last week of July.
u096000@lanl.gov (Roger A. Cole) (04/12/90)
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 <drh@cs.duke.edu> Peter Montgomery <pmontgom@math.ucla.edu> 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
phg@cs.brown.edu (Peter H. Golde) (04/13/90)
In article <48461@lanl.gov> u096000@lanl.gov (Roger A. Cole) writes: >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 ># define assert(expr) \ > ((void)((expr) || assertFail(__FILE__, __LINE__))) >#else ># define assert(expr) >#endif Many C compilers will not put identical string literals into the same piece of storage. Using this method results in many copies of the file name in your data space. You by be more efficient by using: #ifdef DEBUG static char _CurrentFileName = __FILE__; #define assert(expr) \ ((void)((expr) || assertFail(_CurrentFileName, __LINE__))) #else #define assert(expr) #endif When doing this, you have to guard against including the .h file multiple times. --Peter Golde (phg@cs.brown.edu)
ka@cs.washington.edu (Kenneth Almquist) (04/14/90)
An implementation of assert() for UNIX should call abort() rather than exit() when the assertion fails. This way, a core dump will be generated and you can debug the code without having to reproduce the error condition. Kenneth Almquist