allbery@ncoast.UUCP (Brandon Allbery) (07/27/86)
Expires: Quoted from <273@watmath.UUCP> ["Re: C Compiler bug (and fix for a different one)"], by rbutterworth@watmath.UUCP (Ray Butterworth)... +--------------- | I'll trade for a fix for a problem with void functions and the ?: operator. | | void f3(which) | { | extern void f1(),f2(); | which?f1():f2(); | } | cc(1) gives an "incompatible types" error. +--------------- That's not a bug, it's a feature. Literally. Before you start complaining, consider that the intent of functions returning (void) is that of: #define procedure void procedure f1(x, y) { ... } ++Brandon -- ---------------- /--/ Brandon S. Allbery UUCP: decvax!cwruecmp! / / /|\/ Tridelta Industries, Inc. ncoast!tdi2!brandon ---- -------- /-++ 7350 Corporate Blvd. PHONE: +1 216 974 9210 / / /---, ---- Mentor, Ohio 44060 SYSOP: UNaXcess/ncoast / / / / / / -- HOME -- (216) 781-6201 24 hrs. / / / / / / 6615 Center St. Apt. A1-105 ARPA: ncoast!allbery% ---- -----~ ---- Mentor, Ohio 44060-4101 case.CSNET@csnet-relay
rgenter@BBN-LABS-B.ARPA (Rick Genter) (07/30/86)
Before we go overboard on this issue, let's take a look at a couple of
points:
1) C has no "function call statement". Instead it has an "expression"
statement which allows for constructs such as:
printf ("Hello world\n");
ptr ++;
x + 57; /* useless, but allowed */
blatz ();
2) Functions may be declared to be of type (void). For example,
void
blatz ()
{
printf ("Hello world\n");
}
Obviously, given 1) and 2), certain exceptions relating to the use of values
of type (void) have been implemented. Yes, you can not have:
void x ();
int y;
y = x ();
since you are really trying to "use" a (void) value. However, I claim that
it should be as legal to say:
z ? x () : y ();
as it is to say:
if ( z )
x ();
else y ();
when both x and y return (void), given that C *already knows how to throw away
the value of a (void)*.
--------
Rick Genter BBN Laboratories Inc.
(617) 497-3848 10 Moulton St. 6/512
rgenter@labs-b.bbn.COM (Internet new) Cambridge, MA 02238
rgenter@bbn-labs-b.ARPA (Internet old) linus!rgenter%BBN-LABS-B.ARPA (UUCP)
Yow! Now I get to think about all the BAD THINGS I did to a BOWLING BALL
when I was in JUNIOR HIGH SCHOOL!
sam@think.COM (Sam Kendall) (07/31/86)
In article <1327@ncoast.UUCP> allbery@ncoast.UUCP (Brandon Allbery) writes: > > [Example in which expr ? f() : g() is used, where f and g return void.] > > That's not a bug, it's a feature. Literally. > > Before you start complaining, consider that the intent of functions returning > (void) is that of: > > #define procedure void > > procedure f1(x, y) { > ... > } This is misleading. Pascal and allied languages have a more restrictive philosphy than C does; yet you are implying that because procedures are allowed only as statements in Pascal, void-valued expressions should be that way in C. void expressions are always allowed as the left operand of the comma operator, and as the first and third expressions in the for statement; should be ban them there, too? More graphically, should we cut off the hands of burglars in the U.S. because it's done that way in Iran? Enough cheap shots. Here is an example of using void in "?:". Suppose you are writing a stdio-like package, and one function has this specification: void PutC(Stream, char); But for efficiency you want to write PutC as a macro. So you define it like this: extern void _WriteBuf(Stream); #define PutC(s, c) (--(s)->count >= 0 ? (void) (*(s)->bufp++ = (c)) \ : _WriteBuf(s, c)) One can also easily come up with examples where void is useful as the right operand of the comma operator, another place where it is illegal in many compilers. As far as I can figure, void is only useful in these contexts within macros, and the reason it is useful there is that you want to make macros syntactically expressions whenever possible, so you need to use whatever control flow you can within expressions. All in all, the ANSI committee did right in allowing void in these places! --- Sam Kendall sam@godot.think.com Thinking Machines Corp. ihnp4!think!sam
jon@amdahl.UUCP (Jonathan Leech) (07/31/86)
In article <5826@think.COM>, sam@think.COM (Sam Kendall) writes: > ... > Enough cheap shots. Here is an example of using void in "?:". Suppose > you are writing a stdio-like package, and one function has this > specification: > > void PutC(Stream, char); > > But for efficiency you want to write PutC as a macro. So you define it > like this: > > extern void _WriteBuf(Stream); > #define PutC(s, c) (--(s)->count >= 0 ? (void) (*(s)->bufp++ = (c)) \ > : _WriteBuf(s, c)) I won't argue with the desirability of being able to do this, but I believe your example is poorly chosen. I/O functions can always fail and some indication of this ought to be returned to the user where possible, even though few bother to check usage like this. -- Jon Leech (...seismo!amdahl!jon) UTS Products / Amdahl Corporation __@/
farren@hoptoad.uucp (Mike Farren) (08/01/86)
I don't know - maybe I'm just slow. My understanding was that the reason for the existence of "void" was to indicate to the compiler that a function did not return a value, therefore no provisions need be taken to supply one, potentially freeing up a register. Is this wrong? At any rate, you can replace the "expr?(void):(void)" construction with something on the order of "if(expr)(void); else (void);" with only a slight increase in keystrokes, and make your compiler a lot happier. -- ---------------- Mike Farren hoptoad!farren "Tickle my funnybone!"
rbutterworth@watmath.UUCP (Ray Butterworth) (08/01/86)
> In article <5826@think.COM>, sam@think.COM (Sam Kendall) writes: > > void PutC(Stream, char); > > But for efficiency you want to write PutC as a macro. So you define it > > like this: > > extern void _WriteBuf(Stream); > > #define PutC(s, c) (--(s)->count >= 0 ? (void) (*(s)->bufp++ = (c)) \ > > : _WriteBuf(s, c)) > I won't argue with the desirability of being able to do this, but > I believe your example is poorly chosen. I/O functions can always fail > and some indication of this ought to be returned to the user where > possible, even though few bother to check usage like this. > -- Jon Leech (...seismo!amdahl!jon) But you don't know what _WriteBuf() does (neither do I). But we are working on a stdio-like library at waterloo which looks very similar to this. Checking the value returned by putchar() every single time can be quite a waste, especially when you consider that the only time a bad value can be returned is when the buffer is flushed, and that only happens every 8000 characters or so using full buffering on stdout. It is also a pain to have to write the code to do this check and to write whatever code is required to handle the problem (usually print a message and exit), especially if putchar is used in many different places in the code. That is why "few bother to check usage like this". The solution we use is to have the _WriteBuf function, instead of returning an indication of error, either print the message and exit itself, or "raise an event" that can be trapped in the user's code (signals or longjumps). That way users never need to check the return status since the io function can never fail (or if it does it doesn't return). It makes writing correct code much simpler, and makes previously incorrect code correct. It also gets rid of those thousands of useless status checks while adding no extra overhead. Of course this method isn't necessarily the best to use in all cases, only in almost all cases.
rbutterworth@watmath.UUCP (Ray Butterworth) (08/02/86)
I've received mail or news from the following people, all strongly protesting that my fix for void functions and the ?: operator was invalid and that the current behaviour of most compilers is correct, and that the proposed ANSI standard is as mixed up as I am. > From: aka@cbrma.UUCP (Andy Kashyap) > From: allbery@ncoast.UUCP (Brandon Allbery) > From: ark@alice.UucP (Andrew Koenig) > From: hamilton@uxc.CSO.UIUC.EDU (Wayne Hamilton) > From: jsdy@hadron.UUCP (Joseph S. D. Yao) > From: <utzoo!dciem!msb> > From: seismo!ll-xn!mit-amt!mit-eddie!ci-dandelion!jim (Jim Fulton) > From: whp@cbnap.UUCP (W. H. Pollock x4575 3S235) First I'd like to clear up some confusion. We seem to be arguing about slightly different things. In a statement such as i = e1 ? e2 : e3; there are actually three distinct points where the compiler can make checks. At the "=", it must make sure that the types of "i" and the result of the "?:" expression are the same or at least compatible for an assignment. At the "?" it must make sure that e1 is in fact something that can be tested against 0. At the ":" it must make sure that the types of e2 and e3 are the same or are at least compatible. Now, my "fix" was to the check at the ":". Rather than allowing that the two types may both be void perhaps it would have been better to have done a simple test that any type is allowed as long as they are both the same. After all, the error message at this point does say that the two operands of the ":" are incompatible with each other, when in fact they are not. For instance this fix solves the problem of using "?:" with pointers to void functions that was reported earlier. Even this shouldn't cause people to get upset. The complaints were actually about what happens (or rather doesn't happen) at the "?". This is where the check that people want should go. At this point the compiler could check that the right operand (in the parse tree) is in fact a valid expression and not void. This is the point at which a message such as "'?' operator returns void expression" could be produced. If we added that (see below) as well as leaving in the earlier fix, it would get rid of the misleading "incompatible" message and still produce an error that will keep these people happy. > From: aka@cbrma.UUCP (Andy Kashyap) > A function call is an expression and can, therefore, be used anywhere an > expression can be used. When you declare a function (void), you state that > you intend to use that function as a statement instead, that you do not > intend to use it in any operations. It can now only be used where a statement > can. Keep in mind that an expression is a statement, but NOT vice-versa. > > If you look up the reference section of K&R, somewhere it says something > like this (I don't have my K&R with me): > expression -> expression ? expression : expression > Thus you can not use a statement (ie (void) f1()) where an expression is > expected. That did it. You've finally convinced me of the error of my ways. The "?:" expression has the same value and type as the two expressions around the ":". This type cannot be void. I'm converted. I've seen the light. I'm studing the Bible much more closely. And look what I've found: comma-expression -> expression , expression The type and value of the result are the type and value of the right operand. DMR, Chapter 7, Verse 15. But, isn't that what it says about the "?:" expression too? Hallelujah! I've found a sin in our compiler. It actually allows those evil "void expressions" on the right of a comma. Why, this means we'll have to go back through the last few weeks of news, take all those articles talking about void functions and "?:" and repost them, this time changing the "?:" to ",", since all the arguments in them hold equally well. In the meantime, I urge everyone (well, those mentioned above anyway), to put the follwing fix into their compilers. I'm sure it will find many occurrences of this sin that may have crept into their code over the years. void f3(which) { extern void f1(),f2(); which?f1():f2(); } cc(1) gives an "incompatible types" error. In /usr/src/lib/mip/trees.c add the lines indicated by "->>". ... opact( p ) NODE *p; { ... switch( optype(o=p->in.op) ){ ... case QUEST: /* Look, the two cases are already common. */ case COMOP: /* This must prove the devine intent. */ ->> #ifdef I_HAVE_BEEN_SAVED ->> if (!mt2) uerror("'%s' returns void expression", opts[o]); ->> #endif ... case COLON: if( mt12 & MENU ) return( NCVT+PUN+PTMATCH ); else if( mt12 & MDBI ) return( TYMATCH ); ... else if( mt12 & MSTR ) return( NCVT+TYPL+OTHER ); ->> else if ( mt1==mt2 ) return TYPL; break; case ASSIGN:
greg@utcsri.UUCP (Gregory Smith) (08/03/86)
In article <1983@watmath.UUCP> rbutterworth@watmath.UUCP (Ray Butterworth) writes: >I'm converted. I've seen the light. I'm studing the Bible much more >closely. And look what I've found: > > comma-expression -> expression , expression > The type and value of the result are the type and value > of the right operand. > DMR, Chapter 7, Verse 15. > >But, isn't that what it says about the "?:" expression too? >Hallelujah! I've found a sin in our compiler. It actually >allows those evil "void expressions" on the right of a comma. >Why, this means we'll have to go back through the last few >weeks of news, take all those articles talking about void >functions and "?:" and repost them, this time changing the >"?:" to ",", since all the arguments in them hold equally >well. > >In the meantime, I urge everyone (well, those mentioned above anyway), >to put the follwing fix into their compilers. I'm sure it will find >many occurrences of this sin that may have crept into their code over >the years. BUT... BUT .... BUT... you mean I can't void a(),b(),c(); for( a(), b(); ... ; a(), c() ){ ???????????????????? I can't even i = ( a(), b(), 2 ); /* b() on RHS of 1st , */ ??? This is getting worse! ( I strongly suspect Mr Butterworth agrees with me...) There are two things going on in this debate: (1) whether the Powers That Be condone e?void:void (2) whether it makes sense and should be allowed. I haven't the foggiest about (1) but I believe strongly that it *should* be allowed. Herein is described in brief my belief system pertaining to this. It is based on CONTEXTS, not an original idea by any means. There are 4 contexts of importance in C: The void context, the conditional context, the value context, and the lvalue context. - Any expression in a void context is evaluated for side-effects only. - Any expression in a conditional context is evaluated to see if it is zero or non-zero. ( this is only significant if the expression has a logical-valued operator at its top level ). - The value of an expression in a value context is always used. - There is another context, the Lvalue context, which will only serve to cloud this particular issue, so I will ignore it, and lump it under 'value'. Let EV = expr in void context EC = expr in cond context EX = expr in value context. Then STMT ::= EV; | while( EC ) STMT | do STMT while( EC ); | for( EV; EC ; EV )STMT | switch( EX )STMT | if(EC) STMT | if(EC) STMT else STMT | return EX; The above are all of the statement forms containing expressions (maybe I missed a few ). The operators also create a particular context: expr ::= EC && EC EC || EC ! EC EC ? EE : EE <<< looky EV, EE <<< here < any other unary op> EX EX < any other binary op> EX EX[EX], EX(), EX++,EX--, EX(EX,..) etc etc ( here's the point ) I haven't said what EE is. Any expression in an EE context has the same effective context of the ',' or '?:' operator containing it. I.e. the context is inherited. Thus: if( x? p: q ) i ? a : b ; Since the first ?: is in a EC context, p and q are in conditional contexts. Likewise 'a' and 'b' are in void contexts. The expressions e1 and e2 below are in a value context: x = ( i<j? e1 : e2 ); Following this system, lint should complain about the second ?: below but not the first: int func(i){ void p(),q(); i ? p(): q(); /* value of p or q thrown away */ return (i==7)? p(): q(); /* value of p or q returned */ } What really surprises me about all this... is that I always thought the above was the Way It Is. The e?void:void form is useful as the LHS of a comma op within an expression ( esp with macros ). As has been pointed out so nicely by rbutterworth, the VOID,VOID usage is very common, too. if( foo) i=0, j=0; /* i=0 and j=0 both in void context */ j = ( i=0, x+y ); /* x + y in value context */ also, if you write (p(),q(),...) in any context, you expect to be allowed to use a void q(), right? But q() is on the RHS of the first ,: <groups as> ((p(),q()),...) So the context of q() should be inherited through the inner ',' from the LHS of the other ',' I.e., a void context. This is much more than a language purity point - this kind of analysis is necessary to generate decent testing code. If 'x<y' appears in a conditional context, then the code merely sets the condition codes. If it is in a value context, it must give 0 or 1. ( It is usually treated in effect as 'x<y?1:0', i.e. the exp. is forced into a conditional context). Also, consider if( i? j: !k ) foobar(); which comes out as ( VAX 4.2 BSD , no -O ) tstl -4(fp) ; test i jeql L9998 ; if i==0 goto L9998 { and test k } tstl -8(fp) ; test j jneq L9999 ; if j != 0 goto L9999 ( and call foobar()) jbr L16 ; if( i true but j false ) skip it L9998: tstl -12(fp) ; i is false. is k true? jneq L16 ; skip if i false and k true. L9999: calls $0,_foobar L16: This is clearly optimal, and the compiler has obviously treated j and !k in conditional contexts. Note the '9999' label numbers from the code generator (L16 is from the parser for the 'if' ). If this system is used, then the '!' operator will be 'free' in a conditional context ( on almost any machine ). Lint obviously does *some* analysis of this, since it can say "constant in conditional context". BTW, I have a program that gives that error, but the expression is in an ARRAY DIMENSION for crissake. char foo[ THING1 > THING2 ? THING1 : THING2 ]; Obviously it's going to be a constant. Any lint authors listening? Turn that off when a constant expression is called for. While I'm wishing for things, I wanna see 'assignment in conditional context'. We all know why by now... So what's the story? Isn't this The Way It Is? Doesn't the above make a whole lotta sense? Why the *^&%#^&%&^@ would e?void:void _in_a_void_ _context_ be considered linty? -- "You'll need more than a Tylenol if you don't tell me where my father is!" - The Ice Pirates ---------------------------------------------------------------------- Greg Smith University of Toronto UUCP: ..utzoo!utcsri!greg
simon@cstvax.UUCP (Simon Brown) (08/04/86)
In article <1327@ncoast.UUCP> allbery@ncoast.UUCP (Brandon Allbery) writes: > >| void f3(which) >| { >| extern void f1(),f2(); >| which?f1():f2(); >| } >| cc(1) gives an "incompatible types" error. > >That's not a bug, it's a feature. Literally. > Is this the same feature that disallows f(which) { extern void f1(), f2(); void (*fp)(); fp = which? f1 : f2; (*fp)(); } which gives the same "incompatible types" error, but which can't be explained as a confusion between functions and precedures. -- ------------------------------------------------- | Simon Brown, Dept. of Computer Science, | | Edinburgh University | | ...!mcvax!ukc!cstvax!simon | -------------------------------------------------
rbutterworth@watmath.UUCP (Ray Butterworth) (08/07/86)
> Lint obviously does *some* analysis of this, since it can say > "constant in conditional context". BTW, I have a program that gives > that error, but the expression is in an ARRAY DIMENSION for crissake. > char foo[ THING1 > THING2 ? THING1 : THING2 ]; > Obviously it's going to be a constant. Any lint authors listening? > Turn that off when a constant expression is called for. While I'm > wishing for things, I wanna see 'assignment in conditional context'. > We all know why by now... ------/usr/src/lib/mip/cgram.y------(look for "con_e:") /* EXPRESSIONS */ con_e: { #ifdef LINT {extern int constexp;} constexp=1; #endif $<intval>$=instruct; stwart=instruct=0; } e %prec CM ={ $$ = icons( $2 ); instruct=$<intval>1; #ifdef LINT constexp=0; #endif } ; .e: e ------/usr/src/lib/mip/trees.c------(look for warning message) case BRANCH: ccwarn: #ifdef LINT {extern int constexp;} if (hflag&&!constexp) werror("constant in conditional context"); #endif case PLUS: ------/usr/src/usr.bin/lint/lpass1.c------(add to definitions of externals) int constexp=0; ------/usr/src/usr.bin/lint/lpass1.c------(in function contx()) contx( p, down, pl, pr ) register NODE *p; register *pl, *pr; { *pl = *pr = VAL; if (p->in.type==UNDEF) down=VAL; /* (void) cast */ switch( p->in.op ){ case NOT: *pl=down; case ANDAND: case OROR: if (hflag&&(p->in.right->in.op==ASSIGN)) werror("Possible unintended assignment"); case QUEST: *pr = down; case CBRANCH: if (hflag&&(p->in.left->in.op==ASSIGN)) werror("possible unintended assignment"); break; case SCONV: case PCONV:
henry@utzoo.UUCP (Henry Spencer) (08/08/86)
> we are working on a stdio-like library at waterloo which looks very > similar to this. Checking the value returned by putchar() every > single time can be quite a waste... > The solution we use is to have the _WriteBuf function, instead of > returning an indication of error, either print the message and > exit itself, or "raise an event" that can be trapped in the user's > code (signals or longjumps). [This wins big.] On the whole I think this an excellent idea... but Ray, is it too late to convince the people involved *not* to use signals for communications? This has long been recognized as a bad idea. And it's so unnecessary in this case, and related ones: it suffices to say that whenever the _WriteBuf function fails, it calls (say) _WBerror with some suitable parameters, and the user can substitute his own _WBerror function if he doesn't like the default one. This gets much of the benefit without the various problems and unportabilities of signals. -- Henry Spencer @ U of Toronto Zoology {allegra,ihnp4,decvax,pyramid}!utzoo!henry