[comp.lang.c] assert

Dizio@udel.edu (03/12/87)

I found this macro for assert in <assert.h> under ULTRIX 1.2
under the SYSTEM_FIVE #ifdef

#define assert(EX) if (EX) ; else _assert("EX", __FILE__, __LINE__)
                                          ^culprit

It fails when given given an expression containing '"'.  
  eg. assert ((fp = fopen("filename","r")) != NULL)

My question is this.  Is this a proper implementation of this
functionality.  I'm not sure why it wants to pass the EX to
_assert except perhaps to print the expression out,  but in reality
the line number and file name is all I ever need to know.

This is especially true since assert is mainly a debugging tool
and not anything I would put into release software.  Mainly
because one generally would want more control over the action
to take, and the message printed to the user.

The non-system define is

#define _assert(ex) \
	{if (!(ex)) {\
		fprintf(stderr,"Assertion failed: file %s, line %d\n",\
			__FILE__, __LINE__);\
		exit(1);\
	}}

and of coarse if has no problem with any valid expression.

I realize this is a minor point as I could simply write my own macro,
but I just wondered if the proposed standard has specified whether
any valid expression should be able to be used inside the assert,
Or only expressions which don't contain '"'.

Dave

gwyn@brl-smoke.ARPA (Doug Gwyn ) (03/14/87)

In article <4907@brl-adm.ARPA> Dizio@udel.edu writes:
>#define assert(EX) if (EX) ; else _assert("EX", __FILE__, __LINE__)
>                                          ^culprit
>It fails when given given an expression containing '"'.  
>  eg. assert ((fp = fopen("filename","r")) != NULL)
>
>My question is this.  Is this a proper implementation of this
>functionality.  I'm not sure why it wants to pass the EX to
>_assert except perhaps to print the expression out,  but in reality
>the line number and file name is all I ever need to know.

The problem is that there is no way in K&R C to construct
a string literal with a macro argument embedded in it.
When Reiser implemented his preprocessor (used by every
version of UNIX that I have seen so far), he supported
this so that macros such as assert() would work (except
for cases such as the one you stumbled across).  The
CTRL() macro one often encounters uses a similar trick
to construct a character constant.

X3J11 recognized the need for some such facility, but
could not bring themselves to bless this Reiserism,
which conflicts with much existing practice on non-UNIX
systems.  They therefore introduced a "stringize" operator
(but not a "charize" one, alas).  I don't recall if the
Draft Standard points out the need to escape " characters
in the macro argument when stringizing, but I think it
does (I left my copy at home).

drw@cullvax.UUCP (Dale Worley) (03/19/87)

Dizio@udel.edu writes:
> I found this macro for assert in <assert.h> under ULTRIX 1.2
> under the SYSTEM_FIVE #ifdef
> 
> #define assert(EX) if (EX) ; else _assert("EX", __FILE__, __LINE__)
>                                           ^culprit
> 
> It fails when given given an expression containing '"'.  
>   eg. assert ((fp = fopen("filename","r")) != NULL)
> 
> I realize this is a minor point as I could simply write my own macro,
> but I just wondered if the proposed standard has specified whether
> any valid expression should be able to be used inside the assert,
> Or only expressions which don't contain '"'.

The standard requires that assert() work for any expression.  But, if
you want to write your own assert(), you can do things like

#define	assert(EX)	( (EX) ? _assert(#EX, __FILE__, __LINE__)

ANSI C won't let you substitute parameters inside quoted strings, but
it will let you convert a parameter into a string literal via #.  # is
also required to handle things containing "s correctly.  E.g.,

	#define stringify(EX)	#EX
	stringify(printf("Foo\n"))

yields

	"printf(\"Foo\\n\")"

Dale
-- 
Dale Worley		Cullinet Software
UUCP: ...!seismo!harvard!mit-eddie!cullvax!drw
ARPA: cullvax!drw@eddie.mit.edu
Un*x (a generic name for a class of OS's) != Unix (AT&T's brand of such)

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

mcdaniel@amara.uucp (Tim McDaniel) (04/13/90)

u096000@lanl.gov (Roger A. Cole) wrote a good article about assert().
assert() doesn't get enough respect: it can be a BIG lifesaver, and a
significant help in documentation!  (As Roger pointed out.)

In the article, he gave a definition
   #ifdef DEBUG
   #   define assert(expr) ((void)((expr) || assertFail(__FILE__, __LINE__)))
   #else
   #    define assert(expr)
   #endif

However, one of the examples given was
   assert(strlen(name) < 20), printf("%s\n", name);
If DEBUG were not defined, this would expand to
   , printf("%s\n", name);
which is not syntactically valid.  Making the second choice be
   #    define assert(expr)	((void) 0)
would suffice.

(Personally, I prefer to print the expression that failed, which could
help if I wrote two assertions on one line.  I also like to give a
constant string as an explanation argument, for non-obvious checks:
	assert(frobozz(queue) != xyzzy(reg), "pipeline lost a datum");
If, instead, you put an explanatory comment there, the effect would be
more-or-less identical, but requiring a second argument forces the
programmer to provide an explanation.  Personal style.)

--
Tim McDaniel
Applied Dynamics International, Ann Arbor, MI
Internet: mcdaniel%amara.uucp@mailgw.cc.umich.edu
UUCP: {uunet,sharkey}!amara!mcdaniel