[comp.lang.c] Null pointers and constant zero

bill@proxftl.UUCP (T. William Wells) (07/03/88)

There is a very common confusion about zero and null pointers,
that they are the same thing. This has been reenforced by the
fact that on many machines this assumption will always work.

Zero is not the same thing as a null pointer. Both K&R and X3J11
have language which asserts that:

	Assigning a constant zero to a variable is equivalent to
	storing a null pointer of the appropriate type there.

	Comparing a pointer to a constant zero is equivalent to
	comparing with a null pointer of the appropriate type.

	Casting a constant zero to a pointer type creates a null
	pointer of the appropriate type.

It does not say ANYWHERE that the integer (or long) constant
zero and a given null pointer are the same.  It also does not
say that any other zero than a constant zero can be used for
these assignments.  These are very subtle differences.

On many machines, a null pointer is represented by a bit pattern
of all zeros. So also is zero. Thus, you will see code like:

	struct foo {
		char    *x;
	} bar;
	zero(&bar, sizeof(bar));

which assumes that x is now the null pointer. It is not. Instead,
x has been initialized to a bit pattern of all zeros. On a
machine where the null pointer is represented by a bit pattern
of zeros, the code will work correctly. On other machines,
anything can happen.

So, what does this mean for the C programmer?

Constructs like:

	foo     *p;
	bar     *q = 0;
	bar     *r = (bar *)0;

	p = 0;
	p = (foo *)0;
	if (p) ...              /* equivalent to if (p != 0) ... */
	if (p == 0)
	if (p == (foo *)0)
	if (p != 0)
	if (p != (foo *)0)

work because they involve the operators for which the conversions
from a constant zero to a null pointer is valid.

Constructs like:

	foo     *p;
	int     x;

	x = 0; p = x;
	x = 0; p = (foo *)x;
	x = 0; if (p == x) ...
	x = 0; if (p == (foo *)x) ...
	x = 0; if (p != x) ...
	x = 0; if (p != (foo *)x) ...

do not work. The examples without casts fail because the
conversion from an integer to a pointer is illegal in C, your
compiler should give you a compile-time error message. The
examples with a cast do not work because they produce a pointer
to address zero, which MIGHT happen to also be the address used
to represent a null pointer, but which might not.

One final gotcha for C programmers. This code is broken:

===
{
	func(0);
}
func(p)
bar     *p;
{
	===
}

Here is why: the programmer intended that a null pointer be
passed as the value of bar. However, what he actually passed was
a value equal to zero. Now, should the compiler have null
pointers whose representation has the same bit pattern as the
value zero, all will be well (and this is true for many
machines); but should they not, some random pointer value will
get passed.  If one is using Standard C, one should use
prototypes for functions, this fixes the problem. If not, a zero
which is being passed to a function and which is intended to be
converted to a null pointer should always be explicitly cast to
the null pointer.

With this in hand, it is now possible to say what the value of
NULL should be. First, NULL is intended as something which can be
used to represent a null pointer. This is NOT possible because
there is no such thing as THE null pointer. The best that can be
done is to have something which can be used where an unadorned
zero would be valid: as an operand to assignment, comparison, or
cast.  This means that the proper value for NULL is zero, or,
possibly, for Standard C, ((void *)0).

Note that this does not fix the problem with functions; calling
func(NULL) is as wrong as calling func(0). If NULL is defined as
0, they are equivalent. If NULL is defined as ((void *)0), there
is nothing that guarantees that the ((void *)0) has the same
representation as bar *.

A personal note: I do not use NULL at all, because I do not
think the confusion created by its use is worth the minimal or
nonexistent improvement in program clarity.

beckenba@cit-vax.Caltech.Edu (Joe Beckenbach) (07/07/88)

In article <402@proxftl.UUCP> bill@proxftl.UUCP (T. William Wells) writes:
>There is a very common confusion about zero and null pointers,
>that they are the same thing. This has been reenforced by the
>fact that on many machines this assumption will always work.

	`0' cast to pointers makes a null pointer of that type.
This means that NULL === (cast) 0. Standard practice is to say
NULL === 0, then cast the typeless. [Or just ignore it, since the
compiler is `supposed to cast things correctly anyway' :-| ]
This echos Mr. Wells' article.
 
>Note that this does not fix the problem with functions; calling
>func(NULL) is as wrong as calling func(0). If NULL is defined as
>0, they are equivalent. If NULL is defined as ((void *)0), there
>is nothing that guarantees that the ((void *)0) has the same
>representation as bar *.
>
>A personal note: I do not use NULL at all, because I do not
>think the confusion created by its use is worth the minimal or
>nonexistent improvement in program clarity.

	Given all the above, I have decided long since to let the
compiler do all the worrying about the actual bits. Another poster
has also pointed out that THE null pointer does not exist, simply
a null pointer for each type. My encapsulization of the whole mess:

( previously given #define NULL 0   [or whatever] )
#define NIL(type) (type) NULL

and never-after do I even think of NULLs, only NIL pointers of the
appropriate type. This dramatically improves clarity to my eye, and 
solves the typeless-constant problem to boot.
-- 
Joe Beckenbach	beckenba@csvax.caltech.edu	Caltech 1-58, Pasadena CA 91125
Mars Observer Camera Project			Caltech Planetary Sciences Division
Ground Support Engineering, programmer