NULL etc.

Chris Torek chris at mimsy.UUCP
Sun Jul 3 06:36:44 AEST 1988


>In article <6966 at cup.portal.com> Paul_L_Schauble at cup.portal.com asks:
>>is #define NULL (char *)0 really portable??

In article <3458 at rpp386.UUCP> jfh at rpp386.UUCP (John F. Haugh II) answers:
>YES, YES, YES.  by DEFINITION it is portable. ...
>Chris - here is another topic for your posting.

I suppose so, because the answer is `no', or at least, not without more
context.

C's untyped nil pointer, which MUST be given a type before it can be
used correctly, is written as `0' (and `0L', and possibly using
constant integer expressions, depending on whose definition you use;
but `0' suffices and must work).

After it has been given a type (`(char *)0') it becomes a nil pointer
of that type.  Once it has a type (if we ignore some fine points in the
dpANS, many of which are unlikely to be implemented in current C
compilers) it may not be used as a nil pointer of another type.  Hence
(char *)0 is a nil pointer to char, and as such may not be used as a
nil pointer to int, or a nil pointer to struct tcurts, or indeed as
anything other than a pointer to char.  It may work---indeed, it is
more likely to work than to fail---but it is incorrect and unportable,
and should (and does in PCC) draw at least a warning from the compiler.

There are only two ways that the untyped nil pointer can acquire a
type, namely assignment and comparison.  Casts are a special case of
assignment, as are arguments to functions that have prototypes in
scope.  Where this causes the most trouble is in arguments to functions
that do not have prototypes in scope, or for which the prototype does
not specify a type for that argument: e.g., execl():

	f() {
		void execl(char *, ...);

		execl("prog", "prog", "arg1", "arg2", ___);
	}

The only correct way to fill in the blank is with (char *)0 (or
possibly (char *)0L and similar tricks; outside of obfuscated C
contests, these tricks are not worth considering).

The dpANS has at present one more instance of an `untyped' nil pointer,
namely `(void *)0'.  This is an anomaly in the type system, and, while
it has some beneficial properties, I believe that overall it makes the
situation worse, not better.  The differences between using `0' and
`(void *)0' as a `generic nil' are, first, that while 0 is also an
integer constant, (void *)0 is not, and second, that (void *)0 is also
a typed nil pointer (ouch!---more below).

Suppose that NULL is defined as either `0' or `(void *)0'---one of the
two untyped nil pointers---but that we do not know which one.  Which of
the following calls are correct?

	/* defintions before the fragments (note lack of prototypes) */
	void f1(cp) char *cp; { <code> }
	void f2(ip) int *ip; { <code> }
	void f3(vp) void *vp; { <code> }

	...
		f1(NULL);		/* call 1 */
		f1((char *)NULL);	/* call 2 */
		f2(NULL);		/* call 3 */
		f2((int *)NULL);	/* call 4 */
		f3(NULL);		/* call 5 */
		f3((void *)NULL);	/* call 6 */

It is easy to see that calls 2, 4, and 6 (which cast their arguments
and hence provide types) are correct.  The surprise is that while calls
1, 3, and 5 are all wrong if NULL is defined as `0', calls 1 and 5 are
both correct, or at least will both work, if NULL is defined as
`(void *)0'.  Call 3 is wrong in any case.

We can get away with `f1((void *)0)' only because of a technicality:
the dpANS says that (void *) and (char *) must have the same
representation (which more or less means `must be the same type'), and
because (void *) is a valid pointer type, (void *)0 must be a valid nil
pointer of type `void *', and thus must also be a valid nil pointer of
type `char *'.  (Actually, this argument glosses over a subsidiary
technicality, in that there is no guarantee that there is only ONE
valid nil pointer of any given type, but that way lies madness.  There
are more arguments about whether `same representation' implies
`indistinguishable'; these, too, are best left untouched.)

There are no ANSI-conformant C compilers, for there is as yet no ANSI C
standard.  One should therefore assume that code may have to run under
a compiler where NULL is defined as `0', not as `(void *)0', and should
therefore avoid calls like 1, 3, and 5 above.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris at mimsy.umd.edu	Path:	uunet!mimsy!chris



More information about the Comp.lang.c mailing list