[comp.lang.c] Why NULL is 0 - summary

pablo@polygen.uucp (Pablo Halpern) (03/20/88)

A while ago I wrote a couple of postings that suggested solutions to
the dilema of how to define NULL.  I got a number of responses, both by
EMAIL and posted on the net which lead to me retract my position.  As
this discussion has been going on for a while, I thought I would
summarize  what I have learned (from Chris Torek and others) in the
hopes that we might soon put and end to it.

I'll start by restating the problem as I understand it.  I will use
a semi-formal notation.  Given the following declarations:

	typedef foo sometype;
	sometype *p;
	extern void func();	/* note: no argument prototype */

In a separate file:

	void func(arg)
	sometype *arg;		/* same definition of sometype as above */
	{
	...
	}

The problem is to find a definition for NULL which will allow the following
statements and expressions to be syntactically and semantically correct:

	1) p = NULL;
	2) p == NULL
	3) func(NULL);

Some suggested solutions to this problem were:

	a) #define NULL 0
	b) #define NULL 0L
	c) #define NULL ((void *) 0)
	d) (paraphrasing my own suggestion)
	   #if LONG_POINTER
	   #define NULL 0L	/* if sizeof(sometype *) == sizeof(long) */
	   #else
	   #define NULL 0	/* if sizeof(sometype *) == sizeof(int) */
	   #endif

All four solutions work equally well for expressions 1) and 2).  The
real difficulty is in getting expression 3) to work.  Unfortunately
none of the above solutions will work in *all* environments and there
are some not-so-rare environments where *none* of the above solutions
would work.  The reason for this is that they all make one or more of the
following assumptions about the machine and/or compiler which are not
guaranteed by either K&R or ANSI-XJ311:

	a), b), and d) assume that the bit representation of integer zero
	is the same as the bit representation of nil for every pointer type.

	a), b), and d) assume that a pointer is the same width as some
	integral type.

	a), b), and d) assume that all pointer types are the same width.  This
	assumption is false on PR1MEs (old compiler) and 8086 middle memory
	model code where function pointers are different than data pointers.

	The previous assumption does not apply to c), but c) does
	assume that all pointers are all coerced to the same width and
	representation when passed as function arguments.

In other words, these solutions fail because, in the absence of a
prototype, the compiler does not know the width or representation of
the formal parameter and cannot coerce the argument appropriately.  It
has been suggested, by myself and others, that pointers should be
coerced to a "universal pointer" representation when passed as a
function argument in the absence of a prototype, in much the same way
that shorts are coerced to ints.  This would allow solution c) to work
but could add a great deal of conversion overhead, as the forced float to
double coersion has already shown.  c) also has the attribute that a
compiler that supports (void *) is also likely to support prototypes.
This leads me to the only two solutions that would always work:

	e) ALWAYS use function argument prototypes. 
	f) ALWAYS cast NULL when passing it as a function argument
	   in the absence of a prototype.

These solutions work with any reasonable definition of NULL.  Of
course, if your compiler does not support argument prototypes, then f)
is the only portable solution.  e) and f) can, of course, be used
together.

I and many of my fellow programmers have been spoiled by machines like
the 680x0, where all of the above assumptions are true.  Even when we
worked on an 8086, we used the large memory model, where all pointers 
are the same width.  In the code I looked at, NULL was virtually never
used with a cast.  Our code is supposed to be portable but most of our
compilers don't support prototypes.  Therefore, I have adopted solution
d) until we can get those casts in.  I am not a sloppy coder (on the
contrary, I consider myself very neat and disciplined), I just didn't
quite see the light early enough on this one :-(.  Time to stop ignoring
those messages from lint!

Someone once complained that ANSI should not have put prototypes into
the standard because, while they don't break existing code, they don't
codify existing practice either.  With the type of code shown in
expression 3) extremely common, prototypes are good because they can
be used to actually repair formerly non-portable code.  I'm glad they
did it!

By summarizing my thoughts here, I hope I made things better rather than
worse.  If you post a follow-up, please also e-mail a reply, since I may
soon put this entire subject into my kill file.  A big THANK YOU to all
who helped me sort this out!

Pablo Halpern		|	mit-eddie \
Polygen Corp.		|	princeton  \ !polygen!pablo  (UUCP)
200 Fifth Ave.		|	bu-cs      /
Waltham, MA 02254	|	stellar   /

ok@quintus.UUCP (Richard A. O'Keefe) (03/21/88)

This is painfully bad style, but I have often seen[*] code which did e.g.

        char x = NULL;

What was intended, of course, was something like
        #define NUL '\0'
                                /* ok for EBCDIC and ASCII both */
        char x = NUL;
but what was written was technically correct and had the intended effect.

NULL is 0, not 0L, and not (sometype*)0.  Just 0.
I don't use plain NULL for anything, always
        #define NullFoo ((Foo*)0)
        ...
        #define NullBaz ((Baz*)0)

[*] No it _wasn't_ my code!