jwp@larry.sal.wisc.edu (Jeffrey W Percival) (05/11/89)
I would like to find out how the "experts" feel about a coding practice, so I don't develop a bizarre style (bizarre with respect to the general body of usage). I hope this doesn't start a philosophy war of some kind. The situation is, suppose I have a bunch of routines that are packaged into a lib.a, and there is an associated include file "lib.h" that users need to include. Should the lib.h file contain extern declarations for everything in lib.a, or not? case 1: shotgun approach. declare all the routines contained in lib.a advantage: user doesn't need to. disadvantage: "scope" is not micro-managed. labels are in scope everywhere, even labels not used in actual references. case 2: "anal" approach. no declarations in lib.h. User needs to type in the declarations in every function that makes a reference. advantage (???): user sees declarations in same file as usage. scope of labels is managed according to program design. If my goal is to be portable and non-eccentric, what should I do? -- Jeff Percival (jwp@larry.sal.wisc.edu)
scs@sloth.pika.mit.edu (Steve Summit) (05/11/89)
In article <179@larry.sal.wisc.edu> jwp@larry.sal.wisc.edu (Jeffrey W Percival) writes: >The situation is, suppose I have a bunch of routines that are packaged >into a lib.a, and there is an associated include file "lib.h" that >users need to include. Should the lib.h file contain extern declarations >for everything in lib.a? By all means. I'd say that this is the recommended standard practice. If you're using ANSI-style function prototypes, put the prototypes in too, of course. If you're not using prototypes, you may leave out extern declarations for those functions returning int. (You may also leave them in, for documentation purposes.) >disadvantage: "scope" is not micro-managed. labels are in scope >everywhere, even labels not used in actual references. I've never worried about micro-managing global scope. If it's extern, it's global, so any module may call it. Explicitly announcing their intention to call it (with their own extern declaration) doesn't provide much more information than does the actual call. (For the same reason, I think that extern declarations in local scopes are silly. I understand that many people like them, but I've never really understood why.) >case 2: "anal" approach. no declarations in lib.h. User needs >to type in the declarations in every function that makes a reference. >advantage (???): user sees declarations in same file as usage. >scope of labels is managed according to program design. The disadvantage here, which is that the declaration (and, worse, the prototype, if used) has to be replicated, with ripe possibility for error, especially if the declaration has to be changed. This disadvantage, I would think, far outweighs any loss of "micro scoping" caused by header file externs. Steve Summit scs@adam.pika.mit.edu
rwberry@hubcap.clemson.edu (Robert W Berry) (05/11/89)
From article <11318@bloom-beacon.MIT.EDU>, by scs@sloth.pika.mit.edu (Steve Summit): > In article <179@larry.sal.wisc.edu> jwp@larry.sal.wisc.edu (Jeffrey W Percival) writes: ... include variable declarations in header file? ... > > By all means. I'd say that this is the recommended standard > practice. If you're using ANSI-style function prototypes, put > the prototypes in too, of course. If you're not using > prototypes, you may leave out extern declarations for those > functions returning int. (You may also leave them in, for > documentation purposes.) Is it? I thought the more general practice was to define the variable types (structures, unions, etc.) and have the programmer declare a variable of the appropriate type for use in his function. Look at file pointers (of type FILE *) that you declare for stream I/O, low-level structures (such as REGS in Turbo C), windowing libraries often have a structure called "window" that is manipulated by the library rountines. I definitely vote yes on having prototypes in the header file (if you're into prototypes that is). But declaring variables there scares me a little. (And there's always the nasty possibity of duplicating a variable name -- although this already happens with certain structure names in header files.) Bob -- -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- -=- Bob Berry -=- PC-Guru's Inc. ! rwberry@hubcap.clemson.edu -=- -=- We are the science of modern motion. ! 803-654-7623 || 803-656-2635 -=- -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
jwp@larry.sal.wisc.edu (Jeffrey W Percival) (05/12/89)
In article <11318@bloom-beacon.MIT.EDU> scs@athena.mit.edu (Steve Summit) writes: >In article <179@larry.sal.wisc.edu> jwp@larry.sal.wisc.edu (Jeffrey W Percival) writes: >>Should the lib.h file contain extern declarations for everything in lib.a? >By all means. >I've never worried about micro-managing global scope. >If it's extern, it's global, so any module may call it. Thanks for the response. Is this the general consensus? Another question on this style: does one include lib.h in the subroutine files that make up lib.a? That is, if lib.h contains an "extern char foo()" and foo.c includes lib.h and then goes on to define foo, is this a problem? -- Jeff Percival (jwp@larry.sal.wisc.edu)
henry@utzoo.uucp (Henry Spencer) (05/12/89)
In article <179@larry.sal.wisc.edu> jwp@larry.sal.wisc.edu (Jeffrey W Percival) writes: >The situation is, suppose I have a bunch of routines that are packaged >into a lib.a, and there is an associated include file "lib.h" that >users need to include. Should the lib.h file contain extern declarations >for everything in lib.a, or not? Yes. It's more convenient to the user to have all the declarations gotten right for him (especially significant when ANSI prototypes become common) than to have to do them himself, even though this theoretically allows him to conserve name space. Having to type such things in yourself almost invariably leads to shortcuts and sloppiness, too. -- Mars in 1980s: USSR, 2 tries, | Henry Spencer at U of Toronto Zoology 2 failures; USA, 0 tries. | uunet!attcan!utzoo!henry henry@zoo.toronto.edu
gwyn@smoke.BRL.MIL (Doug Gwyn) (05/12/89)
In article <179@larry.sal.wisc.edu> jwp@larry.sal.wisc.edu (Jeffrey W Percival) writes: >The situation is, suppose I have a bunch of routines that are packaged >into a lib.a, and there is an associated include file "lib.h" that >users need to include. Should the lib.h file contain extern declarations >for everything in lib.a, or not? If the library is naturally divisible into smaller packages (e.g. array operations, error reporting, ...) then provide a separate header for each "package" that defines/declares things for just that package. Otherwise, define/declare them all in lib.h. If functions in lib.a can call other functions in lib.a, the user has to be aware of the total set of external names defined by the library anyway so that he doesn't accidentally usurp one for his application. (That would break functions that called that usurped function.) Just because a header includes a declaration of a function does not mean that the function will be linked into the executable image; only if it (or some other external defined in the same object module) is referred to will the corresponding object module be linked in. (There are some linkers that violate this model, but it's pretty near universally agreed that they're broken.)
jwp@larry.sal.wisc.edu (Jeffrey W Percival) (05/12/89)
In article <10251@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn) writes: >In article <179@larry.sal.wisc.edu> jwp@larry.sal.wisc.edu (Jeffrey W Percival) writes: >>Should the lib.h file contain extern declarations for everything in lib.a? >Otherwise, define/declare them all in lib.h. If functions in lib.a >can call other functions in lib.a, the user has to be aware of the >total set of external names defined by the library anyway so that he >doesn't accidentally usurp one for his application. (That would break >functions that called that usurped function.) This appears to be the consensus, and it makes sense. Thanks. And besides, I think I thought up another reason: what if the maintainer of the lib.a decides to re-implement an existing function as a macro, for example. If the user has the thing appearing in an extern statement, it might break. If the user relies on the lib.h file to "make everything OK", then the macro/function sleight of hand can proceed behind the scenes. Is that interpretation unflawed? -- Jeff Percival (jwp@larry.sal.wisc.edu)
mat@mole-end.UUCP (Mark A Terribile) (05/12/89)
> I would like to find out how the "experts" feel about a coding practice, so > I don't develop a bizarre style (bizarre with respect to the general body of > usage). I hope this doesn't start a philosophy war of some kind. It will, never fear. I hereby fire the first salvo. > ... suppose I have a bunch of routines ... packaged into a lib.a, and there > is an associated include file "lib.h" ... Should the lib.h file contain > extern declarations for everything in lib.a, or not? > case 1: shotgun approach. declare all the routines contained in lib.a > advantage: user doesn't need to. > disadvantage: "scope" is not micro-managed. labels are in scope > everywhere, even labels not used in actual references. Advantage: the declarations are available and they are *RIGHT*. When the system is changed or ported, the correct versions are available to all programs that use them. This is important for obvious reasons; it's even more important for ANSI C. > case 2: "anal" approach. no declarations in lib.h. User needs > to type in the declarations in every function that makes a reference. > advantage (???): user sees declarations in same file as usage. Not an advantage. The programmer has to get them right and he's got no machine checking (save, possibly for LINT, which, IMnsHO, is a botch and a disfigurement). > scope of labels is managed according to program design. How is this an advantage? If you really feel that you need it, use #ifdef's to surround ``reasonable'' groups of function names--and #else's controlling #define's that will make the programs invalid if they need a declaration and don't have it. > If my goal is to be portable and non-eccentric, what should I do? If your goal is to write reliable software, which should you do? -- (This man's opinions are his own.) From mole-end Mark Terribile
diamond@diamond.csl.sony.junet (Norman Diamond) (05/12/89)
In article <180@larry.sal.wisc.edu> jwp@larry.sal.wisc.edu.UUCP (Jeffrey W Percival) writes: >Another question on this style: does one include lib.h in the >subroutine files that make up lib.a? Yes. >That is, if lib.h contains >an "extern char foo()" and foo.c includes lib.h and then goes >on to define foo, is this a problem? In K&R-I and ANSI and any non-broken compiler, it is specifically permitted. (Although K&R-I indirectly contradicts itself on this point, it directly states that this is permitted.) -- Norman Diamond, Sony Computer Science Lab (diamond%csl.sony.co.jp@relay.cs.net) The above opinions are my own. | Why are programmers criticized for If they're also your opinions, | re-implementing the wheel, when car you're infringing my copyright. | manufacturers are praised for it?
andre@targon.UUCP (andre) (05/12/89)
In article <180@larry.sal.wisc.edu> jwp@larry.sal.wisc.edu.UUCP (Jeffrey W Percival) writes: >Another question on this style: does one include lib.h in the >subroutine files that make up lib.a? That is, if lib.h contains >an "extern char foo()" and foo.c includes lib.h and then goes >on to define foo, is this a problem? No problem, it is just what you want, if the global prototype is used in the file where the function is defined, the compiler will complain when the prototype and the definition mismatch, thus by including lib.h in the lib.a modules you let the compiler check all your prototypes for you. -- ~----~ |m AAA DDDD It's not the kill, but the thrill of the chase. ~|d1|~@-- AA AAvv vvDD DD Segment registers are for worms. ~----~ & AAAAAAAvv vvDD DD ~~~~~~ -- AAA AAAvvvDDDDDD Andre van Dalen, uunet!mcvax!targon!andre
henry@utzoo.uucp (Henry Spencer) (05/12/89)
In article <180@larry.sal.wisc.edu> jwp@larry.sal.wisc.edu.UUCP (Jeffrey W Percival) writes: >... That is, if lib.h contains >an "extern char foo()" and foo.c includes lib.h and then goes >on to define foo, is this a problem? In the absence of errors like type mismatches, no problems will ensue. In fact, catching things like type mismatches is a very strong reason to do exactly this. -- Mars in 1980s: USSR, 2 tries, | Henry Spencer at U of Toronto Zoology 2 failures; USA, 0 tries. | uunet!attcan!utzoo!henry henry@zoo.toronto.edu
vlcek@mit-caf.MIT.EDU (Jim Vlcek) (05/14/89)
In article <181@larry.sal.wisc.edu> jwp@larry.sal.wisc.edu.UUCP (Jeffrey W Percival) writes: >what if the maintainer >of the lib.a decides to re-implement an existing function as a macro, >for example. If the user has the thing appearing in an extern statement, >it might break. If the user relies on the lib.h file to "make everything >OK", then the macro/function sleight of hand can proceed behind the scenes. >Is that interpretation unflawed? If what was once a function is turned into a macro, programs which passed arguments with side effects to the function may not work correctly with the macro. You know the tune: #define square(x) ((x)*(x)) /* Used to be a function ... needed speed */ y = square(++i); Any macro->function sleight of hand had best not take place behind the scenes, and in fact should probably not take place at all. Going the other direction is probably considered safe, although some programs may have depended upon the side effects of the macro which is now a function. Probably anyone who does so deserves to suffer, though... Jim Vlcek (vlcek@caf.mit.edu uunet!mit-caf!vlcek)
henry@utzoo.uucp (Henry Spencer) (05/16/89)
In article <2337@mit-caf.MIT.EDU> vlcek@mit-caf.UUCP (Jim Vlcek) writes: >If what was once a function is turned into a macro, programs which >passed arguments with side effects to the function may not work >correctly with the macro... Normally it's necessary for such macros to be written carefully so that they invoke each argument exactly once. Given that, and the constraints it puts on what can be done in a macro, though, the transformation doesn't usually cause trouble. -- Subversion, n: a superset | Henry Spencer at U of Toronto Zoology of a subset. --J.J. Horning | uunet!attcan!utzoo!henry henry@zoo.toronto.edu
garys@bunker.UUCP (Gary M. Samuelson) (05/16/89)
In article <181@larry.sal.wisc.edu> jwp@larry.sal.wisc.edu.UUCP (Jeffrey W Percival) writes: >And besides, I think I thought up another reason: what if the maintainer >of the lib.a decides to re-implement an existing function as a macro, >for example. If the user has the thing appearing in an extern statement, >it might break. If the user relies on the lib.h file to "make everything >OK", then the macro/function sleight of hand can proceed behind the scenes. Not necessarily. There are other cases where changing a function to a macro can cause problems: First, if a function is replaced with a macro, it is no longer in lib.a. Therefore, programs have to be recompiled, as opposed to merely relinking. This can be ameliorated by well-written make files (assuming make is available), but should be taken into account when considering changing a function to a macro. Second, a pointer to a function can be passed as a parameter, stored in a variable, and so forth. Any such usage will break with a macro. Third, the size of the program increases as you replace functions with macros. Maybe that's not an issue in your particular case, but in some cases it is, and should be taken into account. Debugging is also affected adversely; ever try to put a breakpoint at a macro? In short, please do not change things by "sleight of hand" or "behind the scenes." If you (generic "you" here) change something I use, I want to know as precisely as possible why, preferably in advance. And if you are inclined to say that a particular change won't have any effect on me, well, I prefer that you let me be the judge of that. Gary Samuelson
mcdaniel@uicsrd.csrd.uiuc.edu (Tim McDaniel) (05/17/89)
In article <5134@bunker.UUCP> garys@bunker.UUCP (Gary M. Samuelson) writes: >First, if a function is replaced with a macro, it is no longer in lib.a. What's to prevent it? In f.c (to go into lib.a): #include <lib.h> #undef f int f(int x, char * y) { ... } That way, it's in lib.h as a macro and lib.a as a function. >Therefore, programs have to be recompiled, as opposed to merely relinking. Not in this scheme. Recompiling would cause some speedup, however, as well as increasing the code size. >Second, a pointer to a function can be passed as a parameter, stored in >a variable, and so forth. Any such usage will break with a macro. Not in pANS C. If "foo" is a macro that requires arguments, and a use of the token "foo" is not followed by a "(" token, it's not a macro call. I just tried this under gcc: #include <stdio.h> int foo(x) int x; { printf("function %d\n", x); } #define foo(y) printf("macro %d\n", y); baz(p) int (*p)(); { (*p)(10); } main() { int (*p)() = foo; (*p)(5); baz(foo); foo(15); exit(0); } You should get: function 5 function 10 macro 15 (unless I screwed up in editing this for posting). So, in lib.h: extern int f(int x, char * y); #define f(x,y) ...whatever... The latter gets used for calls, the former for pointer references. Alas, "cc" under BSD 4.3 on a VAX doesn't follow that rule. In that case, in usercode.c: #include <lib.h> ... some code ... #undef f int (*p)() = f; ... more references to f as a parameter or as a ptr to func After the #undef, all references to f are function calls, not macros -- c'est la vie. Or define a convention for lib.a: ptr_f is an extern const variable which contains a pointer to f. Personally, I use pointers to functions so infrequently that I'm willing to type a few extra trivial keystrokes to use them. Your milage may vary. >Third, the size of the program increases as you replace functions >with macros. Maybe that's not an issue in your particular case, but >in some cases it is, and should be taken into account. A tradeoff of time versus space. If it's a problem in your code (many calls * large size), use #undef. >Debugging is also affected adversely; ever try to put a breakpoint >at a macro? #undef and recompile. I usually have to recompile with -g anyway. If the problem is the macro itself or its calls (for example, side effects in the argument list), perhaps it was a mistake to make it a macro in the first place. >In short, please do not change things by "sleight of hand" or "behind >the scenes." If you (generic "you" here) change something I use, >I want to know as precisely as possible why, preferably in advance. >And if you are inclined to say that a particular change won't have >any effect on me, well, I prefer that you let me be the judge of that. I suppose that when your vendor upgrades your OS, you read the diffs? :-) Seriously, a user can't and shouldn't be aware of *everything* that's going on in the implementation -- that's why libraries and data encapsulation/hiding are so useful. -- Tim, the Bizarre and Oddly-Dressed Enchanter mcdaniel@uicsrd.csrd.uiuc.edu {uunet,convex,pur-ee}!uiucuxc!uicsrd!mcdaniel mcdaniel%uicsrd@{uxc.cso.uiuc.edu,uiuc.csnet}
henry@utzoo.uucp (Henry Spencer) (05/18/89)
In article <5134@bunker.UUCP> garys@bunker.UUCP (Gary M. Samuelson) writes: >...if a function is replaced with a macro, it is no longer in lib.a. Not necessarily. Standard library functions in fact are required to be in both places. Doing this for other functions would also seem sensible. >Third, the size of the program increases as you replace functions >with macros... Not necessarily. Calling sequences can take a non-trivial amount of code, and they tend to interfere with optimizing compilers. The macro version can end up being smaller. >Debugging is also affected adversely; ever try to put a breakpoint >at a macro? This is a generic problem with most forms of optimization. -- Subversion, n: a superset | Henry Spencer at U of Toronto Zoology of a subset. --J.J. Horning | uunet!attcan!utzoo!henry henry@zoo.toronto.edu
garys@bunker.UUCP (Gary M. Samuelson) (05/18/89)
Note that the context of this discussion was the assertion that changing a function to a macro in some library could be done "behind the scenes;" i.e., without the user needing to be aware of the change. In article <1008@garcon.cso.uiuc.edu> mcdaniel@uicsrd.csrd.uiuc.edu (Tim McDaniel) writes: >In article <5134@bunker.UUCP> garys@bunker.UUCP (Gary M. Samuelson) writes: >>First, if a function is replaced with a macro, it is no longer in lib.a. >What's to prevent it? In f.c (to go into lib.a): > #include <lib.h> > #undef f > int f(int x, char * y) { > ... } >That way, it's in lib.h as a macro and lib.a as a function. Then it hasn't been *replaced*, has it? Having the same symbol be both a macro and a function in the same file belongs only in Obfuscated C contest entries. >>Therefore, programs have to be recompiled, as opposed to merely relinking. >Not in this scheme. Recompiling would cause some speedup, however, as >well as increasing the code size. And if later I recompile for some other reason, my program changes in ways which I didn't expect. Also, it is possible that replacing a function with a macro could actually make the program slower. For example, because of its increased size, it page faults or swaps more frequently. >>Second, a pointer to a function can be passed as a parameter, stored in >>a variable, and so forth. Any such usage will break with a macro. >Not in pANS C. If "foo" is a macro that requires arguments, and >a use of the token "foo" is not followed by a "(" token, it's not a >macro call. Alright, there exists at least one compiler for which my statement is false. So, "Any such usage *may* break with a macro." When pANS becomes ANS (i.e., accepted as a standard), are all the existing compilers which don't have these new features suddenly going be transformed, or is someone going to upgrade them (gratis)? >Alas, "cc" under BSD 4.3 on a VAX doesn't follow that rule. Which means that this feature, specified (presumably required) by pANS is inherently non-portable and should therefore not be used. I conjecture that relatively few compilers already incorporate this feature. >In that >case, in usercode.c: > #include <lib.h> > ... some code ... > #undef f > int (*p)() = f; > ... more references to f as a parameter or as a ptr to func >After the #undef, all references to f are function calls, not macros >-- c'est la vie. Or define a convention for lib.a: ptr_f is an extern >const variable which contains a pointer to f. The original contention, which I disputed, was that the function-to-macro transition could be done "behind the scenes." Your illustration supports my position quite well. >Personally, I use pointers to functions so infrequently... We are talking about libraries, which are supposed to be used by lots of people, and therefore should not cater to personal style. > ...that I'm >willing to type a few extra trivial keystrokes to use them. Keystrokes which change the meaning of the program or detract from its readability are not what I would call trivial. >>Debugging is also affected adversely; ever try to put a breakpoint >>at a macro? >#undef and recompile. I usually have to recompile with -g anyway. If >the problem is the macro itself or its calls (for example, side >effects in the argument list), perhaps it was a mistake to make it a >macro in the first place. The very point I was trying to make, and again supports my contention that functions should not be changed "behind the scenes." Such changes should only be made, if ever, after studying a lot of issues. >>In short, please do not change things by "sleight of hand" or "behind >>the scenes." If you (generic "you" here) change something I use, >>I want to know as precisely as possible why, preferably in advance. >>And if you are inclined to say that a particular change won't have >>any effect on me, well, I prefer that you let me be the judge of that. >I suppose that when your vendor upgrades your OS, you read the diffs? >:-) Don't you? Yes, if something has changed which has the potential to affect my entire system, I want to know what changed, so that I can figure out what I have to change. If the new version of OS requires too much work on my part, I won't upgrade, unless the new version includes a new feature or a bug fix I can't live without. I certainly want to see specification changes, lists of problems fixed and features added. > Seriously, a user can't and shouldn't be aware of *everything* >that's going on in the implementation -- that's why libraries and data >encapsulation/hiding are so useful. Changing a function into a macro is not *everything*, but, as you helped illustrate, it has the potential to require the user to make changes in his code, and therefore is something the user needs to be aware of. Gary Samuelson
mcdaniel@uicsrd.csrd.uiuc.edu (Tim McDaniel) (05/19/89)
In article <5178@bunker.UUCP> garys@bunker.UUCP (Gary M. Samuelson) writes: >Note that the context of this discussion was the assertion that >changing a function to a macro in some library could be done "behind >the scenes;" i.e., without the user needing to be aware of the change. I think it *can* be done. Changing a function to a macro must be considered carefully, as with any proposed optimization. In other words, will it really be an optimization, or a problem? *Any* change can break code. - Was the function ever called with arguments with side effects? If so, will the side effects get evaluated exactly once? - Is the product "macro size (in bytes of machine code)" * "lexical number of calls per program" large? How does "macro size" compare with "function call size"? - Were users ever likely to put the function name in jump tables? If so, are their compilers ANSI C compliant: if a macro name is not followed by "(", is it an error? - Who are the users? Dave and Perry down the hall, who know to come to you if they have any problems with -lcsrd, or all of SUN's UNIX customers? - Have you documented the change? >>That way, it's in lib.h as a macro and lib.a as a function. > >Then it hasn't been *replaced*, has it? Hidden, in the sense of variable hiding: {int i; ... {int i; ... }} >Having the same symbol be both a macro and a function in the same >file belongs only in Obfuscated C contest entries. I think there's something in the ANSI C standard about this -- if library functions are implemented as macros or builtins, perhaps? I think #undef is officially blessed for such a case, but I can't remember any details and I can't find it in K&R2. I guess we just have different tastes in coding styles. >>Personally, I use pointers to functions so infrequently... > >We are talking about libraries, which are supposed to be used >by lots of people, and therefore should not cater to personal style. Let me rephrase. "Personally" == "I'd like to know the practices of all the C programmers in the world, but I don't, so I can only list my own experiences." I've see 3 function jump tables in my C programming life, all of which included only program-defined functions (none out of any libraries), 2 of which could have had lists of #undefs added mechanically. As for "lots of people" using libraries: when I think of "libraries", I think of everything from libc.a (millions of users, indirectly) down to libcsrd.a (a local library; dozens of users, maybe). Changing function -> macro works FAR better on a local scale. Looking at your last response, Gary, I'd say we are in substantial agreement about the basic issues involved. -- "6:20 O Timothy, keep that which is committed to thy trust, avoiding profane and vain babblings, and oppositions of science falsely so called: 6:21 Which some professing have erred concerning the faith." Tim, the Bizarre and Oddly-Dressed Enchanter | mcdaniel@uicsrd.csrd.uiuc.edu {uunet,convex,pur-ee}!uiucuxc!uicsrd!mcdaniel mcdaniel%uicsrd@{uxc.cso.uiuc.edu,uiuc.csnet}