Null pointers and constant zero

T. William Wells bill at proxftl.UUCP
Sun Jul 3 06:37:50 AEST 1988


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.



More information about the Comp.lang.c mailing list