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.