[comp.lang.c] Let's define our own NULL

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).