tut@sun.uucp (Bill Tuthill) (02/15/85)
In general, void is A Good Thing. C routines that return a value are like Pascal functions, while C routines not returning values are like Pascal procedures, and should be declared as void to keep things clear. However, some system/library routines, such as close(), fclose(), and free() return values that programs don't usually care about. If the system is unable to close a file, you've got problems that a normal program can't deal with anyway. Nonetheless, lint encourages you to cast the return value of these routines to void. Now, I invite people to prove that the simple fclose(fp); is less portable than: (void)fclose(fp); I consider the first more portable-- many compilers on cheap micros don't know about void. The first version is also more readable-- anytime you throw extra keywords in, legibility decreases. Bill Tuthill
msb@lsuc.UUCP (Mark Brader) (02/17/85)
Consider the function strcpy. The manual here declares this as: char *strcpy(s1, s2) char *s1, *s2; The return value here is just a copy of the input value s1, provided on the off-chance that it'll be useful to the calling program. I claim that all functions that don't otherwise return a value should strive to return a might-be-useful value. Then when you see a function call whose result is unassigned, you know a return value is being ignored. I think (void) should be reserved for really unusual cases, as documentation that you really meant to do that. I do support the use of lint, but I don't like (void). It just clashes with the language's style, to my mind. After all, we can write: a = ++b; or: ++b; but I don't want to write the latter as: (void) ++b; everywhere. What's the difference between this an a function call: increment(&b); ? Mark Brader
ndiamond@watdaisy.UUCP (Norman Diamond) (02/18/85)
> I do support the use of lint, but I don't like (void). It just clashes > with the language's style, to my mind. After all, we can write: > a = ++b; or: ++b; > but I don't want to write the latter as: (void) ++b; > Mark Brader Don't forget that you would also need: (void) a = ++b; because the = operator returns its value too! (Except on one machine I used, where a[1] = a[2] = 1.0; worked fine but a[2*i+1] = a[2*i+2] = 1.0; left a[2*i+1] unchanged.) -- Norman Diamond UUCP: {decvax|utzoo|ihnp4|allegra|clyde}!watmath!watdaisy!ndiamond CSNET: ndiamond%watdaisy@waterloo.csnet ARPA: ndiamond%watdaisy%waterloo.csnet@csnet-relay.arpa "Opinions are those of the keyboard, and do not reflect on me or higher-ups."
ksbszabo@wateng.UUCP (Kevin Szabo) (02/18/85)
In article <1995@sun.uucp> tut@sun.uucp (Bill Tuthill) writes: +-------- |In general, void is A Good Thing. C routines that return a value |are like Pascal functions, while C routines not returning values |are like Pascal procedures, and should be declared as void to keep |things clear. +-------- I thought that just using `return' instead of `return ( expression )' told lint that the function wasn't returning a value, and hence the returned value could be ignored. VOID tells lint that a value is returned but it is ignored. Please correct me if I am wrong! +-------- |However, some system/library routines, such as close(), |fclose(), and free() return values that programs don't usually care |about. If the system is unable to close a file, you've got problems |that a normal program can't deal with anyway. +-------- I agree. I think it would be a useful thing for routines that normally "can't" fail, or return values that are ignored 90% of the time, to provide instead a general `event' handling function. This could probably look a little like the signal handling stuff. Thus we can have event( FAILED_PRINTF, bad_exit ); event( FAILED_MALLOC, garbage_collect ); By default `events' could probably be a PERROR with an exit(1). This scheme is not perfect but is much better than having to VOID every system call that should always work. It is also a lot better than segmentation faults/bus errors that occur when you try to carry on after an unchecked system call does fail. Of course, it adds complexity which is why it wasn't done this way in the first place. After perusing the manual pages on our machine it looks like the Math Faculty Computing Facility (at U of Waterloo) has already made a stab at providing these event handling facilities for their new stuff. I don't know anything about them though. Kevin -- Kevin Szabo watmath!wateng!ksbszabo (U of Waterloo VLSI Group, Waterloo Ont.)
jss@sjuvax.UUCP (J. Shapiro) (02/21/85)
[Pacman's revenge...] The real problem with the use of (void) is that too many current C compilers don't support it, and don't know what the void cast should go to. Many of the best of the microcomputer C compilers don't have it, and there is an argument which runs: "C is small enough and simple enough to run on micros, there is nothing intrinsically wrong with micros (NO FLAMES), and backwards compatability is important." For all of my joking about a ^= b ^= a ^= b, the real reason not to add things to a language is just this. Why should I need to spend another $500 on a good compiler? I am not that rich. (void) is therefore a THORN to portability. Even some UNIX lookalike compilers don't support it. Jon Shapiro Haverford College UNIX is a trademark of big brother.
henry@utzoo.UUCP (Henry Spencer) (02/22/85)
> |In general, void is A Good Thing. C routines that return a value > |are like Pascal functions, while C routines not returning values > |are like Pascal procedures, and should be declared as void to keep > |things clear. > I thought that just using `return' instead of `return ( expression )' > told lint that the function wasn't returning a value, and hence > the returned value could be ignored. VOID tells lint that a > value is returned but it is ignored. Please correct me if I am wrong! There are two different uses of void here: using it to declare that a function returns no value (to the compiler and to human beings, not just to lint), and using it in a cast to throw away an unwanted return value. The former is presumably what Bill is referring to. > |However, some system/library routines, such as close(), > |fclose(), and free() return values that programs don't usually care > |about. If the system is unable to close a file, you've got problems > |that a normal program can't deal with anyway. Actually, note that the asynchronous nature of Unix disk i/o can cause a real, live i/o error to be reported to a close() rather than to the write() that caused it. Programs which really want to be paranoid *will* check the returned value from close() and fclose(). The basic problem here is that most programs want to see an error-free i/o system abstraction, but a few want to know about problems. Splitting these two levels would simplify this problem immensely, but it's a bit late for that now. > I agree. I think it would be a useful thing for routines that normally > "can't" fail, or return values that are ignored 90% of the time, to > provide instead a general `event' handling function... I am deeply suspicious of event-handling primitives; I don't think I have ever seen a good way of doing them. Lots of bad ways, though. One thing which *can* be very handy is variants of the standard routines which are "guaranteed" to work because they abort the program if the standard routine underneath returned an error. The K&P efopen() is a prime example. We also now have an emalloc(), which is an enormous convenience since there is very seldom anything useful to do when malloc fails. (Incidentally, a global s/malloc/emalloc/ is an excellent way of improving the robustness of 4.2BSD code.) Although this sort of thing can't be incorporated into old code just by recompiling, in new code it's much simpler than introducing pseudo-signals and hence asynchronism and all the nightmares that involves. -- Henry Spencer @ U of Toronto Zoology {allegra,ihnp4,linus,decvax}!utzoo!henry
g-frank@gumby.UUCP (02/22/85)
> The real problem with the use of (void) is that too many current C > compilers don't support it, and don't know what the void cast should go to. Try typedef void unsigned ; This is what Whitesmith's did all through the Idris system for the 68000, and it seems to work. Obviously, if your compiler (and lint) don't recognize it, it won't buy you any immediate benefit, but if you go to port your code to an implementation that does use it, your code will be "ready for prime time," as it were. -- Dan Frank Q: What's the difference between an Apple MacIntosh and an Etch-A-Sketch? A: You don't have to shake the Mac to clear the screen.
gwyn@brl-tgr.ARPA (Doug Gwyn <gwyn>) (02/23/85)
> typedef void unsigned ;
Nope. Try
typedef int void;
or
#define void int
(Use "unsigned" if you wish; it shouldn't matter.)
gwyn@brl-tgr.ARPA (Doug Gwyn <gwyn>) (02/25/85)
> One thing which *can* be very handy is variants of the standard routines > which are "guaranteed" to work because they abort the program if the > standard routine underneath returned an error. I much prefer to have a low-level routine return an indication to its invoker when it cannot do its function for whatever reason. Strategy should be managed at higher levels.
henry@utzoo.UUCP (Henry Spencer) (03/03/85)
> > One thing which *can* be very handy is variants of the standard routines > > which are "guaranteed" to work because they abort the program if the > > standard routine underneath returned an error. > > I much prefer to have a low-level routine return an indication to its > invoker when it cannot do its function for whatever reason. Strategy > should be managed at higher levels. You've missed my whole point, Doug. The low-level routines are not pre-empting the decision on how to handle errors, they are aiding in the implementation of the most common decision: "on error, print a message and die". By calling (say) emalloc rather than malloc, the higher levels are signifying their decision to adopt this strategy, and are asking the lower levels to handle the implementation. There is no difference in power or flexibility, only in ease of use. As somebody (John Mashey?) once said: If you want people to follow a standard, it must be easier to be standard than to be non-standard. Note my earlier comment about the usefulness of a global s/malloc/emalloc/ in Berkeley code. By requiring the caller to do the work of checking for success, even when there is nothing meaningful to be done about failure, the bare malloc interface encourages sloppy programmers to ignore the whole issue. It also makes conscientious programmers do repetitive and annoying extra work. Of course, we need the bare interface: sometimes there *is* meaningful action to be taken in the event of failure. But usually, the only thing to do is complain and die. It makes all kinds of sense to package this common case in a form that makes it easy to use, so that people will do it right. It also makes a remarkable difference in ease of use if a program is doing mallocs all over the place; the difference is marked enough to inspire the "why didn't we put that in the library long ago?" feeling that marks a good library routine. Try it, you'll like it. -- Henry Spencer @ U of Toronto Zoology {allegra,ihnp4,linus,decvax}!utzoo!henry
gwyn@brl-tgr.ARPA (Doug Gwyn <gwyn>) (03/06/85)
Re: emalloc() etc. I also often package malloc() in routines like NodeAlloc() etc. that alloc the proper thing (e.g. a (struct node)) and perform the appropriate action when the malloc() fails. However, what the right action in this case really is, is dependent on the application. In production code, it is seldom appropriate to just print an error message and terminate the process. I built an interactive drawing system once that had to keep running in case of ANY foreseeable emergency, in order not to lose the user's hours of work when there was a system failure. In case of running out of memory, it set a flag which would cause display of an "out of memory" message at the appropriate time, and returned failure status from the allocator to its invoker so something sensible could be done to maintain the integrity of the linked data structures. Note that there was no "stderr" to print messages to (I had to arrange for messages to appear in the menu area of the refresh display list), and the message had to be deferred until the whole data structure had been processed, to avoid a cascade of error messages when one would do. I am not saying that such thorough error handling is ALWAYS required, but I would encourage program designers to think carefully about the handling of exceptional conditions wherever they may occur. I fear that providing an emalloc() in the standard library would mainly make it easier for programmers to excuse their not thinking about what to do in the error case. It wouldn't be any worse than not checking for malloc() failure, though. What we really need to do is to figure out some way to stamp out sloppy programming. Any ideas?