[comp.std.c] ANSI assert

joe@proto.COM (Joe Huffman) (09/08/90)

In the latest version of 'The C User Journal' P.J. Plauger wrote about the
assert 'function' under ANSI C.  He outlined various 'sins' of many 
implementations of assert and 'blessed' the following version.  It appears
to me that this is wrong (it would break nearly all of my code).

----
/* assert.h standard header
 * copyright (c) 1990 by P.J. Plauger
 */
#undef assert/* remove any previous definition */
#ifdef NDEBUG

#define assert(test) ((void)0)

#else /* NDEBUG not defined */

void _Assert(char *);

#ifndef _STR /* define stringize macro just once */
#define _STR(x) #x
#endif

#define assert(test) ((test) || _Assert(__FILE__ ":" _STR(__LINE__) " " #test))
#endif
----

My concern is when NDEBUG is defined.  I have many places in my code where I
do something like the following:

  assert(i++ < limit);

  or

  assert(tree_init() == OKAY);

My definition of assert with debugging turned off looks like:

#define assert(test) ((void)(test))

Which allows the action inside the assert to occur.  Did the ANSI committee
overlook this?  Did P.J. Plauger?  Am I in error for doing this?  I realize
that macros can be hazardous (like #define SQR(x) ((x)*(x))) but this doesn't
have to be and yet either someone overlooked a potential problem or my coding
style is going to have to be revised (as well as 10's of thousands of lines
of code). 
 
Can anyone shed some light on this for me?

-- 
joe@proto.com
FAX: 208-263-8772

rex@aussie.UUCP (Rex Jaeschke) (09/08/90)

Joe Huffman writes

> In the latest version of 'The C User Journal' P.J. Plauger wrote about the
> assert 'function' under ANSI C.  He outlined various 'sins' of many 
> implementations of assert and 'blessed' the following version.  It appears
> to me that this is wrong (it would break nearly all of my code).

You raise an interesting point. However, the standard explicitly states 
that if NDEBUG is defined, assert is defined simply as

	#define assert(ignore) ((void)0)

This implies that any side-effects present in the argument are NOT 
seen since the argument expression is NOT evaluated. This appears to 
be confirmed by the fact that the name of the macro's formal parameter 
is `ignore.'

Since assert was done several meetings before I started on X3J11 
perhaps someone else can remember whether this was deliberate.


I wrote an article much the same as Plauger's in the Dec issue of The 
Journal of C Language Translation (Vol 1, #3). Here's a small test 
program (from that article) to check your version of assert.h.

---------------------------------------------------------------------
To check your version, compile the following test program.  No errors
should be produced.

#define NDEBUG
#include <assert.h>

void f(int i)
{
	assert(i);

	i ? assert(i - 4) : assert(i + 4);

	if (i > 24)
		assert(i * 3);
	else
		assert(i * 24);
}

#undef NDEBUG
#include <assert.h>

void g(int i)
{
	assert(i);

	i ? assert(i - 4) : assert(i + 4);

	if (i > 24)
		assert(i * 3);
	else
		assert(i * 24);
}
---------------------------------------------------------------------

Rex

----------------------------------------------------------------------------
Rex Jaeschke     |  Journal of C Language Translation  | C Users Journal
(703) 860-0091   |        2051 Swans Neck Way          | DEC PROFESSIONAL
uunet!aussie!rex |     Reston, Virginia 22091, USA     | Programmers Journal
----------------------------------------------------------------------------
Convener of the Numerical C Extensions Group (NCEG)
----------------------------------------------------------------------------

coleman@twinsun.com (Mike Coleman) (09/08/90)

joe@proto.COM (Joe Huffman) writes:
>My concern is when NDEBUG is defined.  I have many places in my code where I
>do something like the following:
>  assert(tree_init() == OKAY);

>My definition of assert with debugging turned off looks like:

>#define assert(test) ((void)(test))

>Am I in error for doing this? 

Yes, at least the way I understand things.  Nothing which is necessary for the
correctness of your program should be put in an assert.  Only expressions
necessary to *test* the assertion should be in the assert.  This is an easy
mistake to make; I still do it occasionally.  So, the correct version of the
code you give above (as I read it) would be

	result = tree_init();
	assert(result == OKAY);

The point of having the assert in the first place is that you can put checks
for correctness in your program which are extremely expensive, and then remove
them simply by defining NDEBUG.  For example, imagine this piece of code in a
malloc implementation:

	assert(validate_heap());  /* an expensive function which validates the
					heap to within an inch of its life. */

The version of assert you assume does not give the desired effect here.  In
particular, casting an expression to 'void' does *not* allow the compiler to
throw it away, in and of itself.

In any case, if you want this behavior, just define your own macro.  But
please, don't call it 'assert'.

Hope this helps.
-- 
Nothing's ever late when it's measured in Programmer's Time: ++ coleman
  |     |     |     |     |     |     |     |     |          ++ @twinsun.com
BEGIN  1/2   2/3   3/4   4/5   5/6   6/7   7/8   8/9  (etc)  ++ @cs.ucla.edu

scjones@thor.UUCP (Larry Jones) (09/09/90)

In article <1428@proto.COM>, joe@proto.COM (Joe Huffman) writes:
> In the latest version of 'The C User Journal' P.J. Plauger wrote about the
> assert 'function' under ANSI C.  He outlined various 'sins' of many 
> implementations of assert and 'blessed' the following version.  It appears
> to me that this is wrong (it would break nearly all of my code).
> 
> [ Plauger's version which doesn't evaluate the argument when NDEBUG
>   is defined ]
> 
> My concern is when NDEBUG is defined.  I have many places in my code where I
> do something like the following:
> 
>   assert(i++ < limit);
> 
>   or
> 
>   assert(tree_init() == OKAY);

I take it you haven't compiled this code with very many compilers.
As far as I know, assert has always been a macro, and there has
never been any guarantee that it's a safe one!  Your code is
highly non-portable.  X3J11 debated long and loud about whether
the argument should be evaluated when NDEBUG is defined, but the
decision was made that assert under NDEBUG should completely
disappear to avoid any performance hits, despite the fact that
you lose some error checking (i.e. the code might actually be
invalid if NDEBUG wasn't defined).  Code with side-effects in
an assert argument was generally agreed to be perverse at best.
----
Larry Jones                         UUCP: uunet!sdrc!thor!scjones
SDRC                                      scjones@thor.UUCP
2000 Eastman Dr.                    BIX:  ltl
Milford, OH  45150-2789             AT&T: (513) 576-2070
It's going to be a long year. -- Calvin

norvell@csri.toronto.edu (Theo Norvell) (09/09/90)

In article <1428@proto.COM> joe@proto.COM (Joe Huffman) writes:
>
>either someone overlooked a potential problem or my coding
>style is going to have to be revised (as well as 10's of thousands of lines
>of code). 
> 
>Can anyone shed some light on this for me?
>
>-- 
>joe@proto.com
>FAX: 208-263-8772

No one overlooked this problem.  At the September 88 meeting, this very point
came up in response to a request from a prominent computer scientist who
argued much as do you.  It was decided that the least evil was to insist
that the argument not be evaluated when NDEBUG is not defined.

The reason is that it is quite reasonable for the argument to make no sense
when runtime checking is on.  For instance, it may use virtual variables,
variables that need not exist except for checking purposes, as in
	void root(void)
	{
	    #ifndef NDEBUG
		float x_init = x;
	    #endif
	    assert(x <= 0.0) ;
	    ... code to find square root ...
	    assert(abs(x_init - x*x) <= EPS) ;
	}
Note that root is neatly specified by pre and post conditions.

As has been pointed out, you may write your own assert macro.  May I
suggest

#define assert_and_do(x) ((x) || assert(0))

Theo Norvell                                         norvell@csri.toronto.edu

henry@zoo.toronto.edu (Henry Spencer) (09/09/90)

In article <1428@proto.COM> joe@proto.COM (Joe Huffman) writes:
>... I have many places in my code where I
>do something like the following:
>
>  assert(i++ < limit);

This is unwise, and has been from the start.  Many implementations of
<assert.h>, including the V7 one (the original, I think), do not evaluate
the operand at all when NDEBUG is defined.

The final ANSI C document, by the way, *specifically states* that when
NDEBUG is defined, assert() is defined as

	#define	assert(ignore)	((void)0)

and nothing else.
-- 
TCP/IP: handling tomorrow's loads today| Henry Spencer at U of Toronto Zoology
OSI: handling yesterday's loads someday|  henry@zoo.toronto.edu   utzoo!henry

johnl@esegue.segue.boston.ma.us (John R. Levine) (09/09/90)

In article <1428@proto.COM> you write:
>  assert(i++ < limit);

Asserts with side effects have always been a bad idea, as is practically
any macro call with side effects.  Every implementation of assert that I
have ever seen throws away the argument to assert without evaluating it if
NDEBUG is defined.  You have my condolences if this breaks your programs,
but even before ANSI such programs had major portability problems.  If you
ever plan to use a compiler other than the one you're using now, you'd
better plan to fix your code.

This is a famous problem by now, I know lots of programs that couldn't be
shipped with NDEBUG set exactly because there were side effects in the
asserts.  (I think this is an argument for better programmer education
rather than fudging the language to the tastes of sloppy programmers.)

Regards,
John Levine, johnl@esegue.segue.boston.ma.us, {spdcc|ima|world}!esegue!johnl

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/10/90)

In article <1428@proto.COM> joe@proto.COM (Joe Huffman) writes:
>  assert(i++ < limit);
>  assert(tree_init() == OKAY);
>My definition of assert with debugging turned off looks like:
>#define assert(test) ((void)(test))

Your definition of assert() is not standard conforming.  When NDEBUG
is defined before inclusion of <assert.h>, the ONLY conforming definition
for the assert() macro is
	#define assert(ignore) ((void)0)

I understand why you prefer your style, but X3J11 decided on the above.
Thus you need to change your coding style to something like

	assert(i < limit);
	++i;
or

#ifndef NDEBUG
	assert(tree_init() == OKAY);
#else
	(void) tree_init();	/* cast is optional; included for "lint" */
#endif

By the way, here's my public-domain implementation of section 4.2:


SOURCE FOR <assert.h> in the standard C compilation environment:

/*
	<assert.h> -- definitions for verifying program assertions

	public-domain implementation

	last edit:	12-Apr-1990	Gwyn@BRL.MIL

	complies with the following standards:
		ANSI X3.159-1989
		IEEE Std 1003.1-1988
		SVID Issue 3 (except for the extra blank in the NDEBUG case)
		X/Open Portability Guide Issue 3 (ditto)
 */

#undef	assert

#ifdef	NDEBUG

#define	assert(ignore)	((void)0)

#else	/* ! NDEBUG */

#ifdef	__STDC__

extern void	__assert(const char *, const char *, int);

#define	assert(ex)	((ex) ? (void)0 : __assert(#ex, __FILE__, __LINE__))

#else	/* ! __STDC__ */

extern void	__assert();

/* Reiser CPP behavior assumed: */
#define	assert(ex)	((ex) ? (void)0 : __assert("ex", __FILE__, __LINE__))

#endif	/* __STDC__ */

#endif	/* NDEBUG */


SOURCE FOR __assert() in the standard C run-time support library:

/*
	__assert() -- support function for <assert.h>

	public-domain implementation

	last edit:	16-Jan-1990	Gwyn@BRL.MIL

	complies with the following standards:
		ANSI X3.159-1989
		IEEE Std 1003.1-1988
		SVID Issue 3
		X/Open Portability Guide Issue 3
 */

#include	<stdio.h>

extern void	abort();

#ifndef	__STDC__
#define	const	/* nothing */
#endif

void
__assert(expression, filename, line_num)
	const char	*expression, *filename;
	int		line_num;
	{
	(void) fprintf(stderr, "assertion failed: %s, file %s, line %d\n",
		expression, filename, line_num);
	(void) fflush(stderr);

	abort();
	/* NOTREACHED */
	}

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/10/90)

In article <159@thor.UUCP> scjones@thor.UUCP (Larry Jones) writes:
>Code with side-effects in
>an assert argument was generally agreed to be perverse at best.

I don't know that it was "generally agreed"; one can reasonably argue
either side of this issue from the point of view of programming.
However, existing implementation practice was clearly on the side of
not evaluating the argument in the NDEBUG case; by requiring that
method of implementation, the standard guarantees wider usage of
assert() than would be safe were it left undefined whether or not the
argument is evaluated.  (A previous poster gave an example of this.)

ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) (09/11/90)

In article <1428@proto.COM>, joe@proto.COM (Joe Huffman) writes:
> In the latest version of 'The C User Journal' P.J. Plauger wrote about the
> assert 'function' under ANSI C.  ...  It appears
> to me that [Plauger's code] is wrong (it would break nearly all of my code).

> My concern is when NDEBUG is defined.  I have many places in my code where I
> do something like the following:
>   assert(i++ < limit);
> either someone overlooked a potential problem or my coding
> style is going to have to be revised (as well as 10's of thousands of lines
> of code). 

Your code is *ALREADY* broken.  The ``de facto'' standard in UNIX has been
	#ifdef NDEBUG
	#define assert(ex) /**/
	#else
	...
	#endif
or something very like it for a long time.  For many people it has been
important that the test *not* be evaluated when NDEBUG is defined; if
the tests are cheap enough that you want them to be done anyway you
would be rather silly to do all that work and not take advantage of it!
A common case is
	assert(very_costly_checking_call());
where the _point_ of NDEBUG is to avoid the call when not debugging.

The argument of assert() has no business producing side effects.  Quite
apart from assert being a macro rather than a function, assert() is
supposed to be used the way you would use assertions, and assertions
are *pure* boolean formulas.  The "discourse convention" used by human
beings is that assertions are comments about the code, not an integral
part of it, so if you put side effects in your assert()s your _human_
readers are going to be very confused by it.
-- 
Heuer's Law:  Any feature is a bug unless it can be turned off.

karl@haddock.ima.isc.com (Karl Heuer) (09/12/90)

[We agree that ANSI assert() forbids the evaluation of the argument, so the
fun part of this discussion now belongs in alt.lang.cfutures instead.  I'm
redirecting followups.  --kwzh]

In article <3726@goanna.cs.rmit.oz.au> ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) writes:
>In article <1428@proto.COM>, joe@proto.COM (Joe Huffman) writes:
>>[The ANSI definition for ``assert'' breaks my code that does this:]
>>   assert(i++ < limit);
>
>Your code is *ALREADY* broken.  The ``de facto'' standard in UNIX has been
>[to not evaluate the arg if NDEBUG is enabled] for a long time.  For many
>people it has been important that the test *not* be evaluated when NDEBUG is
>defined; if the tests are cheap enough that you want them to be done anyway
>you would be rather silly to do all that work and not take advantage of it!

It's not the *tests* that he wants done unconditionally; just the side
effects.  I discovered (and reported) that an early Draft of the Standard
neglected to specify whether the side effects were forbidden, permitted, or
required; I was personally hoping for ``required'' so that calls like the
above would be sensible.  I backed down when somebody pointed out the
>	assert(very_costly_checking_call());
you mentioned.

>Quite apart from assert being a macro rather than a function,

Irrelevant, since ANSI macros are ``safe'' unless otherwise noted, and it
would have been easy enough to make this one safe if ANSI had required it.

>Supposed to be *pure* boolean formulas...comments about the code, not an
>integral part of it...

That's the common convention, but I think the ``assert(getchar() == '\n')''
example demonstrates that it could be useful to violate it.  The alternative
of ``c = getchar(); assert(c == '\n');'' may well trigger the warning
``variable c assigned but not used''.  And ``#ifdef NDEBUG   (void)getchar();
#else   assert(getchar()=='\n');   #endif'' is too bulky; it's exactly the
sort of thing for which macros are designed--so why not encapsulate it in a
macro, as long as it's not called ``assert''?

Remember my posting of 14-Jan-1988 where I talked about assert()?  I've gotten
nine replies over the last two years and eight months; they don't seem to be
trickling in anymore, so I guess it's time to post the summary: The consensus
is that a change in semantics would have to be accompanied by a change in
name, even though few people admitted to invoking complex (but pure) functions
from an assert().

So anyway, I would now propose the following:

In addition to ``assert'', <assert.h> shall define the macro ``require'',
which always evaluates its argument for side effects, whether or not NDEBUG is
set.  If the argument is true (compares unequal to zero), then the macro
returns no value.  Else, if the NDEBUG macro is not defined, then a message is
printed and the abort() function is invoked.  Else, the behavior is undefined.

The last clause makes require() useful for pure expressions, too.  The
rationale is that the code has been tested without NDEBUG and verified to be
correct; defining NDEBUG means that the user is ready for optimization.
Saying that the behavior is undefined gives the compiler license to assume
that the assertion is indeed true, and it may be able to generate better
subsequent code.  It is noted that current optimizer technology won't handle
anything much more complex than ``require(x == 3)'' or ``require(x == y)'',
but we can hope that this will be improved.  For example, one might be able to
use this to inform a vectoring compiler that two arrays are non-overlapping.

>-- 
>Heuer's Law:  Any feature is a bug unless it can be turned off.

(I *knew* that would be showing up in a .signature within the week.)

Karl W. Z. Heuer (karl@kelp.ima.isc.com or ima!kelp!karl), The Walking Lint

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/13/90)

In article <17964@haddock.ima.isc.com> karl@kelp.ima.isc.com (Karl Heuer) writes:
>So anyway, I would now propose the following:
>In addition to ``assert'', <assert.h> shall define the macro ``require'',

I would like to suggest that all proposals for additions beyond the turf
already staked out by the C standard be done using new headers, since the
#includer of one of the standard headers now knows that it is safe to, e.g.,
freely use the identifier "require" in his current programs, and it would
be horrible for any future revision of the C standard to make changes that
would invalidate currently strictly conforming code.  (Unless a very strong
case could be made for the necessity of that, but clearly there is no strong
necessity of adding unreserved stuff to existing standard headers.)

jimp@cognos.UUCP (Jim Patterson) (09/14/90)

In article <9009082252.AA16290@esegue.segue.boston.ma.us> johnl@esegue.segue.boston.ma.us (John R. Levine) writes:
>In article <1428@proto.COM> you write:
>>  assert(i++ < limit);
>
>Asserts with side effects have always been a bad idea, ...
>
>This is a famous problem by now, I know lots of programs that couldn't be
>shipped with NDEBUG set exactly because there were side effects in the
>asserts.  (I think this is an argument for better programmer education
>rather than fudging the language to the tastes of sloppy programmers.)

Existing C history aside, I think it would be much more robust to
define assert in such a way that such things can't happen. Turning off
debugging shouldn't introduce bugs! Sure, you can blame it on the
programmers, but since C is a language which permits side effects in
conditional expressions, I think the proper function of assert's (i.e.
a mechanism to INCREASE program reliability) would be better served if
they were implemented in such a way as to always evaluate their
argument, as Mr. Levine suggests.

With any decent compiler, this change won't add additional overhead to
most uses of asserts (i.e. where the condition obviously has no side
effects and can be discarded). Where there are actually side effects,
they will take effect; this is extra code but it might also affect
what the program does (i.e., the side effect might be necessary for
correct operation). The only place where it's clearly additional
wasted overhead is where a function call is involved that has no side
effects. 

The big problem, of course, is that it breaks with existing practice.
However, I don't think it's "fudging" the language to suggest that
existing practice might be improved upon.
-- 
Jim Patterson                              Cognos Incorporated
UUCP:uunet!mitel!sce!cognos!jimp           P.O. BOX 9707    
PHONE:(613)738-1440                        3755 Riverside Drive
                                           Ottawa, Ont  K1G 3Z4

jimp@cognos.UUCP (Jim Patterson) (09/14/90)

In article <1990Sep8.164857.2930@jarvis.csri.toronto.edu> norvell@csri.toronto.edu (Theo Norvell) writes:
>As has been pointed out, you may write your own assert macro.  May I
>suggest
>
>#define assert_and_do(x) ((x) || assert(0))

There's a slight problem with this. While it will work, it will print
a rather misleading message with many ANSI implementations of assert (which
print out the failing condition). For example, you might see:

		Failed assertion (0) at line 117 of myfile.c

when what you want to see is:

		Failed assertion (x_init - x*x < EPS) at line 117 of myfile.c

It would be better, though more work and potentially less portable, to copy 
and modify the definition of assert().
-- 
Jim Patterson                              Cognos Incorporated
UUCP:uunet!mitel!sce!cognos!jimp           P.O. BOX 9707    
PHONE:(613)738-1440                        3755 Riverside Drive
                                           Ottawa, Ont  K1G 3Z4

karl@haddock.ima.isc.com (Karl Heuer) (09/19/90)

In article <8795@cognos.UUCP> jimp@cognos.UUCP (Jim Patterson) writes:
>In article <1990Sep8.164857.2930@jarvis.csri.toronto.edu> norvell@csri.toronto.edu (Theo Norvell) writes:
>>#define assert_and_do(x) ((x) || assert(0))
>
>There's a slight problem with this. While it will work, it will print
>a rather misleading message...

The more serious problem is that it won't work, since void expressions can't
be operands of `||'.

>It would be better, though more work and potentially less portable, to copy 
>and modify the definition of assert().

Alternately:
	#if defined(NDEBUG)
	#define require(expr) ((void)(expr))
	#else
	#define require(expr) assert(expr)
	#endif
Karl W. Z. Heuer (karl@kelp.ima.isc.com or ima!kelp!karl), The Walking Lint
________
Exercise for the student: does the error message now contain the user's
string or the constant "expr"?  Prove your answer using 3.8, then check your
favorite ANSI compiler to see if it agrees.  Be prepared to defend your
reasoning in the form of a bug report.