mark@navtech.uucp (Mark Stevans) (06/23/88)
Back in the days when I was a contract C programmer, I once got into a disagreement over the advisability of the following idea: "Let's define NULL in our product-wide header file like so: #ifndef NULL #define NULL 0 #endif That way, if someone needs NULL but doesn't need to use the standard I/O library, he won't need to pull in <stdio.h>." Personally, I strongly disfavor this idea for reasons that I will not list here for reasons of impartiality. If you disagree with me, please educate me as to why this might be a good idea. If you agree with me, please give me some definitive and convincing arguments to use if and when I run across this again somewhere. Thanks for your views either way. Mark "Stubborn" Stevans
davidsen@steinmetz.ge.com (William E. Davidsen Jr) (06/23/88)
In article <160@navtech.uucp> mark@navtech.uucp (Mark Stevans) writes: | Back in the days when I was a contract C programmer, I once got into a | disagreement over the advisability of the following idea: | | "Let's define NULL in our product-wide header file like so: | | #ifndef NULL | #define NULL 0 | #endif | | That way, if someone needs NULL but doesn't need to use the standard | I/O library, he won't need to pull in <stdio.h>." I know I'll get flamed for disagreeing with K&R, but this is WRONG. The original book was written before segmented archetectures were commonly used, and the idea of "near" and "far" pointers was not an issue. When defining NULL as zero, you open the possibility of errors in argument lists terminating with NULL, since *only* assignment will translate zero to a pointer. Look at the exec family to see where NULL is passed where a pointer is expected. Better is to define NULL: #define NULL ((void *) 0) and be sure it works. Some compilers have all sorts of tests to use zero or zero long, but this always leaves room for a problem in mixed mode programming. Obviously there are ill-written programs which expect NULL to be an int, even though the term "NULL pointer" is used in K&R, even in the index. These programs may break in some obscure ways when a true pointer is used, so my solution is not perfect. For all the people who are going to post saying "ignore the brain-damaged PC," stay in school. People who write software for a living can't ignore ten million possible sales, or the ability to run on really cheap units inhouse. Anyone who lives in that commercial environment must live with it, and anyone who wants to write postable code in general should consider all current and future targets. Okay, I linked my mail file to the asbestos disk, let's hear from you. -- bill davidsen (wedu@ge-crd.arpa) {uunet | philabs | seismo}!steinmetz!crdos1!davidsen "Stupidity, like virtue, is its own reward" -me
gwyn@brl-smoke.ARPA (Doug Gwyn ) (06/23/88)
In article <160@navtech.uucp> mark@navtech.uucp (Mark Stevans) writes: > #ifndef NULL > #define NULL 0 > #endif In fact I do this in a header that I include after all system headers needed for a source module have been included. I use the symbol NULL simply for readability; the macro is never necessary, since 0 is just as good. Of course you still need to cast the null pointer to the appropriate type when using it as a function argument. The only reason for the #ifndef NULL ... #endif is that the implementor of <stdio.h> may have chosen ((void *)0) for the definition, which would clash with my redefinition. #undef NULL would serve just as well. Lately I've been starting to think that plain 0 is just as readable..
kenny@uiucdcsm.UUCP (06/23/88)
/* Written 1:55 pm Jun 22, 1988 by mark@navtech.uucp in uiucdcsm:comp.lang.c */ /* ---------- "Let's define our own NULL" ---------- */ #ifndef NULL #define NULL 0 #endif That way, if someone needs NULL but doesn't need to use the standard I/O library, he won't need to pull in <stdio.h>." /* End of text from uiucdcsm:comp.lang.c */ The ANSI C committee recognized that things like NULL are not part of `standard I/O', and moved NULL to <stddef.h>, instead of <stdio.h>. ptrdiff_t and size_t are there, too. This solution preserves the advantage of having it in a standard header file, so the user doesn't redefine it wrongly, but removes the temptation to `just define it myself since I don't need standard I/O.'
gwyn@brl-smoke.ARPA (Doug Gwyn ) (06/24/88)
In article <11326@steinmetz.ge.com> davidsen@crdos1.UUCP (bill davidsen) writes: >The original book was written before segmented archetectures were >commonly used, ... That's irrelevant. The C language guarantees that null pointers are comparable to the integer constant 0, and that assigning integer constant 0 to a pointer of any type produces a null pointer of that type. Also (actually as a consequence of the assignment rule), casting integer constant 0 to a pointer type produces a null pointer constant of that type. The internal representation of a null pointer is unspecified; in particular it need be neither all zero bits nor the same size as an int. Segmented architectures could even use more than one representation for null pointers of a given type, so long as the language rules are obeyed. #define NULL 0 is a universally correct symbolic definition for null pointers (of unspecified type). Using an ANSI-conforming implementation, #define NULL ((void *)0) is also guaranteed to have the same properties. This obviously won't work when using compilers that don't know about (void *). You still need to cast a null pointer constant, no matter how defined, to have the correct type when using it as a function argument, unless there is a prototype in scope for the function (in which case the automatic argument type coercions will take care of this for you). It seems we go over this every few months. Could everyone PLEASE try to remember this next time? P.S. In response to the original article: No, one needn't include <stdio.h> just to define NULL. You can define your own, or just use 0. If stdio is going to be used in the application anyway, there is no real cost associated with including <stdio.h> unnecessarily, but it IS rather silly to do so.
lbr@holos0.UUCP (Len Reed) (06/24/88)
in article <11326@steinmetz.ge.com>, davidsen@steinmetz.ge.com (William E. Davidsen Jr) says: > > In article <160@navtech.uucp> mark@navtech.uucp (Mark Stevans) writes: > | Back in the days when I was a contract C programmer, I once got into a > | disagreement over the advisability of the following idea: > | > | "Let's define NULL in our product-wide header file like so: > | > | #ifndef NULL > | #define NULL 0 > | #endif > | > | That way, if someone needs NULL but doesn't need to use the standard > | I/O library, he won't need to pull in <stdio.h>." > > I know I'll get flamed for disagreeing with K&R, but this is WRONG. > The original book was written before segmented archetectures were > commonly used, and the idea of "near" and "far" pointers was not an > issue. When defining NULL as zero, you open the possibility of errors in > argument lists terminating with NULL, since *only* assignment will ^^^^^^^^^^^^^^^^ What about initialization? Surely this works: char far * cfptr = 0; > translate zero to a pointer. Look at the exec family to see where NULL > is passed where a pointer is expected. > > Better is to define NULL: > #define NULL ((void *) 0) > and be sure it works. Some compilers have all sorts of tests to use zero > or zero long, but this always leaves room for a problem in mixed mode > programming. > > Obviously there are ill-written programs which expect NULL to be an > int, even though the term "NULL pointer" is used in K&R, even in the > index. These programs may break in some obscure ways when a true pointer > is used, so my solution is not perfect. Such programs also often assume sizeof(char *) == sizeof(int) and even sizeof(long) == sizeof(int)! Brain damage.... > On the PC family I'm using Microsoft 5.x and SCO Xenix. I use function prototypes with typed arguments; I don't see why "0" won't work in all cases. The compilers cast "0" to the proper (far or near) null value. Do you have an example using function prototypes with type, information, where this would fail? Is there a compiler that allows ((void *)0) but not prototypes? If you don't have function prototypes, though, assuming "func" expects (int, pointer, int), x = func(2, ((void *)0), 34); should work for all "memory models" while x = func(2, 0, 34); will fail if you're using multiple data segments, e.g., "large" model. Even ((void *)0) won't get you out of all mixed-mode pointer problems. Try compiling func(2, ((void *)0), 34) small model where func is actually expecting (int, char far *, int)! You've got to have function prototypes for mixed-mode programming unless you're careful and willing to trust to luck. I like the idea of #define NULL ((void *)0) though, since much imported code won't use function prototypes. Also, ((void *)0 will provide better type checking than 0. My compilers will complain if I use a (void *) where something other than a pointer is expected, but they allow 0 to be used (almost?) anywhere. Some folks use NULL when they should say '\0'; ((void *)0) can cause a warning here. Maybe that's just as well: use 0 or '\0' for a null character, but use NULL only for pointers. Microsoft C 5.1 stdio.h defines NULL as 0 or 0L depending upon the model. This can cause spurious loss of precision warning messages when using explicit "near" pointers in the large model, and is thus inferior to ((void *)0). All said, I like ((void *)0) and may adopt it. One additional philosophical reason to use it. (void *) means generic pointer, and thus ((void *)0) means generic pointer with value zero, which is surely closer to "null pointer" than 0. -- - Len Reed
richardh@killer.UUCP (Richard Hargrove) (06/25/88)
In article <8147@brl-smoke.ARPA>, gwyn@brl-smoke.ARPA (Doug Gwyn ) writes: > [ his umpteenth posting in the last year of the correct interpretation of > what NULL means and how to use it properly ] C'mon folks. How many times does Doug [Guy, Henry, Chris, {your favorite comp.lang.c regular}] have to explain this? Is this concept REALLY this difficult? While I normally read anything posted by the regulars, I now skip postings that contain NULL in the Subject:, regardless of author. Am I the only person out here who is tired of the interminable discussion of NULL? I think Doug shows remarkable patience by continuing to respond to this one. He certainly has more than I do. I think we need to start the "= vs. ==" discuusion up again. It's been dormant for about six weeks now... ;-) richard hargrove ...!{ihnp4 | codas | cbosgd}!killer!richardh --------------------------------------------
chris@mimsy.UUCP (Chris Torek) (06/26/88)
>| #define NULL 0 In article <11326@steinmetz.ge.com> davidsen@steinmetz.ge.com (William E. Davidsen Jr) writes: > I know I'll get flamed for disagreeing with K&R, but this is WRONG. >The original book was written before segmented archetectures were >commonly used, and the idea of "near" and "far" pointers was not an >issue. When defining NULL as zero, you open the possibility of errors in >argument lists terminating with NULL, since *only* assignment will >translate zero to a pointer. A cast is an assignment; only incorrect programs (or certain prototype dependent routines, under dpANSish compilers) do not cast NULL in argument lists. > Better is to define NULL: > #define NULL ((void *) 0) >and be sure it works. Some compilers have all sorts of tests to use zero >or zero long, but this always leaves room for a problem in mixed mode >programming. Unfortunately, this still does not work without a cast. If functions f and g take code-space and data-space pointers respectively, then in a large-data-space small-code-space (`medium model', I think) compiler, f(NULL); g(NULL); where NULL is ((void *)0), will pass the wrong number of bytes to at least one of the two functions. The only rule needed for correct code is this: Casts always work. -- In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163) Domain: chris@mimsy.umd.edu Path: uunet!mimsy!chris
daveh@marob.MASA.COM (Dave Hammond) (06/27/88)
In article <11326@steinmetz.ge.com> davidsen@crdos1.UUCP (bill davidsen) writes: >[...stuff deleted...] > Obviously there are ill-written programs which expect NULL to be an >int, even though the term "NULL pointer" is used in K&R, even in the >index. These programs may break in some obscure ways when a true pointer >is used, so my solution is not perfect. > > For all the people who are going to post saying "ignore the >brain-damaged PC," stay in school. People who write software for a >living can't ignore ten million possible sales, or the ability to run on >really cheap units inhouse. Anyone who lives in that commercial >environment must live with it, and anyone who wants to write postable >code in general should consider all current and future targets. Here, here! You can not image the volumes of code I've had to modify when porting programs from larger machines to the 80x86 family. Most ends up with a basic one-line fix: if (ptr==NULL) return(error_value); Sure, its an easy fix, but making a large program just to find out I get a "segmentation violation" (or whatever) and core dump is a real pain. BTW- Don't think that the "large model" C library routines handle this situation. Passing NULL to any of the str*, *printf or ato* families will bomb too. (You'd think Microsoft would *know* about passing zero to far addresses and deal with it :-) Dave Hammond UUCP: ...!marob!daveh --------------------------------
davidsen@steinmetz.ge.com (William E. Davidsen Jr) (06/27/88)
In article <1090@holos0.UUCP> lbr@holos0.UUCP (Len Reed) writes: | > argument lists terminating with NULL, since *only* assignment will | ^^^^^^^^^^^^^^^^ | What about initialization? Surely this works: | char far * cfptr = 0; | | > translate zero to a pointer. Look at the exec family to see where NULL | > is passed where a pointer is expected. Well, I might have used the the "assignment operator," but I think you see what I mean. | On the PC family I'm using Microsoft 5.x and SCO Xenix. I use | function prototypes with typed arguments; I don't see why "0" won't work | in all cases. The compilers cast "0" to the proper (far or near) null | value. Do you have an example using function prototypes with type, | information, where this would fail? Is there a compiler that allows | ((void *)0) but not prototypes? I tried both SunOD and Ultrix. Both barfed at the prototype. I don't have access to a "real BSD" compiler, but I think this might be typical of BSD. The point in case was execl, variable number of args, type char *. What do you use for the prototype of that, and if VOID is zero, does it generate a far pointer when you end a list with NULL in large model? | Even ((void *)0) won't get you out of all mixed-mode pointer problems. I totally agree. What I was saying was that it generally *will* get you out of constant mode problems. | I like the idea of | #define NULL ((void *)0) | though, since much imported code won't use function prototypes. | Also, ((void *)0 will provide better type checking than 0. My compilers | will complain if I use a (void *) where something other than a pointer is | expected, but they allow 0 to be used (almost?) anywhere. A benefit I hadn't considered. | Some folks use NULL when they should say '\0'; ((void *)0) can | cause a warning here. Maybe that's just as well: use 0 or '\0' for | a null character, but use NULL only for pointers. I usually define a symbol, (EOS for end of string) which is '\0'. A good point, anything which gives the compiler a better chance to catch errors is a win. | Microsoft C 5.1 stdio.h defines NULL as 0 or 0L depending upon the model. | This can cause spurious loss of precision warning messages when using | explicit "near" pointers in the large model, and is thus inferior | to ((void *)0). | | All said, I like ((void *)0) and may adopt it. One additional | philosophical reason to use it. (void *) means generic pointer, and | thus ((void *)0) means generic pointer with value zero, which is | surely closer to "null pointer" than 0. | | -- | - Len Reed Although I expected flames, almost all of the responses were technical in nature, and well thought out, such as this one. I did get a few nastygrams, but they were mainly in the nature of "we are tired of discussing NULL," rather than disagreement. -- bill davidsen (wedu@ge-crd.arpa) {uunet | philabs | seismo}!steinmetz!crdos1!davidsen "Stupidity, like virtue, is its own reward" -me
wes@obie.UUCP (Barnacle Wes) (06/28/88)
In article <8147@brl-smoke.ARPA>, gwyn@brl-smoke.ARPA (Doug Gwyn ) writes: > P.S. In response to the original article: No, one needn't include > <stdio.h> just to define NULL. You can define your own, or just use 0. > If stdio is going to be used in the application anyway, there is no > real cost associated with including <stdio.h> unnecessarily, but it IS > rather silly to do so. Or, if you have one of those infinitely programmable text editors, you can set up your editor `init' or `rc' file to automagically insert #define NULL ((void *) 0) anytime is has to CREATE a new file with the extension `.c'. Easy, huh? -- {uwmcsd1, hpda}!sp7040!obie!wes | "If I could just be sick, I'd be fine." +1 801 825 3468 | -- Joe Housely --
guy@gorodish.Sun.COM (Guy Harris) (06/29/88)
> Here, here! You can not image the volumes of code I've had to modify when > porting programs from larger machines to the 80x86 family. Most ends up > with a basic one-line fix: > > if (ptr==NULL) > return(error_value); > > Sure, its an easy fix, but making a large program just to find out I get > a "segmentation violation" (or whatever) and core dump is a real pain. Is this intended to say something about "larger machines" or various memory models? If so, I fail to see what it is; if the one-line fix is adding the "if (ptr == NULL)", this would appear to be required *not* by the representation of null pointers and the "int" value 0 being different on 80x86 C implementations with 16-bit "int"s, but by the fact that while *some* operating systems on *some* "large machines" *currently* allow you to dereference null pointers without getting a core dump, various PC C implementations and OSes don't. Other "large machines" and other operating systems for at least one of the first class of "large machine" do not allow this, and future versions of some members of the first class of operating systems may disallow it as well (other members can be told to disallow it, but they don't do so by default).