[net.unix-wizards] Please use NULL instead of 0 whenever you have a pointer!

dan@ttds.UUCP (Dan Sahlin) (01/13/84)

<blank line>

Someone claimed that NULL was exactly the same as 0.
This is however not true of you have a compiler with 16 bit integers,
and 32 bit addresses.
I have spent a lot of time "debugging" programs that implicitly assume
that pointers and integers have the same length.

Dan Sahlin    (..mcvax!enea!ttds!dan)

guy@rlgvax.UUCP (Guy Harris) (01/14/84)

Actually, in <stdio.h> NULL is defined as 0, which is really as it should
be.  So whether you use 0 or NULL makes no difference (unless somebody
made the mistake of defining NULL as "(char *)0"); you still have to
cast it explicitly.  Then again, that's what "lint" is for; it's a good
idea to run "lint" on any program which isn't a quick throw-away or very
small and written by somebody you *know* won't make type errors - on the
other hand, I don't know anybody like that, myself included.

(If NULL were defined as "(char *)0", "lint" would get gastric distress
whenever NULL were passed as a pointer argument to a routine which expected
an "int *" or something like that.  Furthermore, if you tried to assign
"(char *)0" to an "int *" variable, PCC would justifiably complain about
an illegal pointer combination.  C doesn't have the notion of a generic
"null pointer", it has a "null pointer to char" which is distinct from
"null pointer to int" which is distinct from "null pointer to
(struct proc)"....)

	Guy Harris
	{seismo,ihnp4,allegra}!rlgvax!guy

ron%brl-vgr@sri-unix.UUCP (01/15/84)

From:      Ron Natalie <ron@brl-vgr>

I'm sorry but whoever claimed NULL was the same as zero was correct.
Zero is the only integer you are assured to be able to assign to or
compare with a pointer.  If you can't it is a bug with your C implementation.

-Ron

gwyn%brl-vld@sri-unix.UUCP (01/16/84)

From:      Doug Gwyn (VLD/VMB) <gwyn@brl-vld>

However, zero pointers come in several different sizes in some
implementations of C, with (char *) guaranteed to be the widest.
This means that passing an actual argument of 0 to a function
expecting a pointer may not necessarily work, unless the zero is
explicitly type-cast to the corresponding pointer type.  E.g.,
	func( (struct foo *)0, more_args );
or
	func( (struct foo *)NULL, more_args );
rather than
	func( 0, more_args );
or
	func( NULL, more_args );
if the first formal parameter of foo() is declared a (struct foo *).

kvm@basservax.SUN (Karlos Mauvtaque) (01/18/84)

Dennis Ritchie says that 0 is the 'null pointer'.  Read your reference
manual guys.  There is no place in the language where there is null
pointer ambiguity.  'null pointer's as arguments to function calls
must be cast to the correct pointer type whether you use NULL or 0.
You can't assume that (char *) is the same size as all other pointers so

#define	NULL	((char *)0)

won't do you any good.  I use NULL rather than 0 because it's good style.

mark@cbosgd.UUCP (01/24/84)

If NULL and 0 are supposed to be the same, please explain what proper
behavior is in the following situation:
	execl("/bin/echo", "echo", "hi", "there", 0);
(This is the way it's documented in exec(2) and used everywhere.)
Now, suppose you have a system where ints are 16 bits and char *'s
are 32 bits.  (Hmm, sounds like a 68000.)  Now, it's clear we have a
series of 32 bit values on the stack, followed by a 16 bit zero, followed
(if you keep looking) by garbage.

The only clean solution I know of (other than using 32 bit ints, which
is inefficient) is to use NULL and have NULL be (void *) 0.  This is
obviously pretty painful, given all those programs out there that use
exec.

Considering that it's quite reasonable for either the high 16 bits or
the low 16 bits of a valid 32 bit pointer to be zero, is there some
other reasonable way to handle this?

gwyn%brl-vld@sri-unix.UUCP (01/26/84)

From:      Doug Gwyn (VLD/VMB) <gwyn@brl-vld>

The final argument to execl(2) should not be documented as 0,
that's left over from the days when it didn't matter.  The only
sane thing for the terminator to be is a (char *)0, for uniformity
with the argument list.

ron%brl-vgr@sri-unix.UUCP (01/26/84)

From:      Ron Natalie <ron@brl-vgr>

This is wrong if you want to be strict about things:

	execl("/bin/echo", "echo", "hi", "there", 0);

And so is this!

	execl("/bin/echo", "echo", "hi", "there", NULL);

in system V (and most everything else) as NULL really is 0.

You are only guaranteed of being allowed to mix 0 and pointers
in assignment (that's with an equals, not passing to functions)
and comparison.  The correct way is:

	execl("/bin/echo", "echo", "hi", "there", (char *)0);

Because execl's arguments must be character pointers not ints!

-Ron

john@genrad.UUCP (John Nelson) (02/01/84)

After hearing all the controversy that NULL is identically equal to zero,
the only sane solution is to fix the C language.  Add a special constant
that is the equivalent of zero, but is a pointer.  Since NULL already has
bad connotations, let's borrow from PASCAL, and call it NIL.

Now NIL is guaranteed to be a pointer of the largest type (char * ?) but
it will compare against any pointer without error and it can be passed
as a subroutine argument.  NIL can be assigned to any pointer type, and
will always be guaranteed to be different than a valid pointer.

It would be nice if the definition of NIL included the restriction that
a dereference of NIL would always cause a run-time error, but maybe this
is too much to ask for.

Those of us without the "fixed" compiler can #define NIL ((char *) 0)
and use it anyway (although some of the compilers may complain about
"illegal pointer combination"s)

guy@rlgvax.UUCP (Guy Harris) (02/03/84)

> Since I started this NULL vs. 0 discussion, I perhaps should repeat
> my reasons for wanting people use to NULL instead of 0.

> + 0 is 16 bits on my machine.
> + NULL is declared as (char *)0 on my machine which makes 32 bits.
> + Programs that use 0 as pointer *crash*.
> + Programs that use NULL *don't crash*.
>   The reasons (although very obvious) have been very well elaborated
>   earlier so I will not repeat them.

What fixes the problems caused by your first point (as elaborated by
stating that pointers are 32 bits on your machine), your third point, and
your fourth point is not using 0 vs. NULL, it's using 0 vs (whatever *)0.
NULL solves this problem *only* if it is declared as (char *)0.  It is *NOT*
so declared on our (16-bit int, 32-bit pointer) machine, and it will not be.
The correct way to fix the problem is to run your programs through "lint" and
fix the type disagreements between formal and actual arguments.  After all,
is the point of this exercise to produce programs which don't break in the
immediate environment, or is it to produce programs that are as type-correct
as possible?  Doing the latter brings benefits in terms of code correctness
that the former doesn't bring (I can attest to bugs that have been uncovered
as a result of running a program through "lint" - or even noticing the
warnings that PCC gives you!).

> While hoping to end this NULL discussion soon, I still have a question:
> Is there really a C-compiler that makes a difference between a "(char *)0"
> and "(int *)0" so a program can *crash* if it uses the wrong one?

I'd be willing to bet that C implementations on Data General machines and
on some Honeywell Level 6 minis make that distinction.  There is *NO*
guarantee whatsoever that there will not be such implementations of C; the
language permits them, so if you want to write portable code you *must*
assume that (char *)0 and (int *)0 may be completely incompatible beasts.
Just declaring NULL to be (char *)0 and using it universally is sloppy
coding practice.  Period.  It may patch over the problem on machines with
16-bit ints and 32-bit pointers, but the correct solution (casting 0 or NULL
to the proper value) doesn't cost more than minor programmer inconvenience and
is far more likely to work in general.

	Guy Harris
	{seismo,ihnp4,allegra}!rlgvax!guy

guy@rlgvax.UUCP (Guy Harris) (02/04/84)

> After hearing all the controversy that NULL is identically equal to zero,
> the only sane solution is to fix the C language.  Add a special constant
> that is the equivalent of zero, but is a pointer.  Since NULL already has
> bad connotations, let's borrow from PASCAL, and call it NIL.

> Now NIL is guaranteed to be a pointer of the largest type (char * ?) but
> it will compare against any pointer without error and it can be passed
> as a subroutine argument.  NIL can be assigned to any pointer type, and
> will always be guaranteed to be different than a valid pointer.

> It would be nice if the definition of NIL included the restriction that
> a dereference of NIL would always cause a run-time error, but maybe this
> is too much to ask for.

> Those of us without the "fixed" compiler can #define NIL ((char *) 0)
> and use it anyway (although some of the compilers may complain about
> "illegal pointer combination"s)

Well, #define NIL ((char *)0) will do exactly what the NIL you mention
does.  EITHER ONE will cause compilers to complain - CORRECTLY - about
illegal pointer combinations.

There is no such thing as a "generic pointer" in C.  Period.  As such, there
is no such thing as a generic null pointer in C.  Period.  It may be awkward
on non-byte-addressed machines to implement (char *) and (int *) (or
(anything else *)) so that (char *)&foo and (int *)&foo yield the same bit
pattern.  Furthermore, it is not necessary, as a correct C implementation will
generate code to do the pointer format conversion if one assigns an (int *)
to a (char *), or vice versa - with ONE exception: since there is no way
to tell a C module that a function in another C module ("module" here means
"source file") takes arguments of a certain type, no C compiler can generate
code to do conversion of arguments to functions automatically.  You have to
help the compiler here by putting in explicit casts.

I read a lot of articles on this subject whose real point seems to be that
it's too much trouble to properly cast 0 or NULL when passed as an argument
to a function.  What's so hard about it?  Those of us who've worked on machines
with 16-bit "int"s and 32-bit pointers do it as a matter of habit.  I even
did it *before* I worked on such a machine, just because the minimal extra
effort to write type-correct code (yes, you *can* write code which is 99%
type-correct in C) is worth the effort.  The only thing C "needs" is a way
to declare the types of the arguments to a function, so that the compiler
can generate the casts instead of requiring the user to do so.  However,
one can put in the proper casts explicitly, so C *isn't broken* in this
regard.  People may be too lazy to use C properly, but there is a rather
low limit on the degree to which things should - or even can - be designed so
that people who don't know how to use them won't break them.

Please, people, if you don't yet know that there aren't generic pointers in
C, and therefore that there is no such thing as a generic "null pointer" in C,
and furthermore that there *shouldn't* be such a beast, please go back and
read the C Reference Manual - especially the sections I referred to in an
earlier article.  It's been explained several times here why C doesn't need
to be changed to "fix" the problem with NULL pointers; please don't force it
to be explained again.

	Guy Harris
	{seismo,ihnp4,allegra}!rlgvax!guy

kre@mulga.SUN (Robert Elz) (02/05/84)

If defining a new "magic object" (NIL) would solve the problem
at all, I'm sure that would have been done.

However, the constant '0' already is that magic object, as far
as the current syntax and semantics of C allow there to be one at all.

The compiler cannot pass the "correct" type of null pointer to
a function as an arg, unless (in the arg list) you tell it what
the correct type is, as otherwise it has no knowledge of what that
would be.  It is no good at all to pass the "widest" null pointer,
the only one that will do is the one that's just the right width.
(Given a wider pointer, of known type, its always possible to convert
it to another, narrower, pointer, but something has to do that
conversion.  Just dumping a wide pointer on the stack and hoping that
its going to mean the right thing in a called function simply won't work).

Should C be altered so that the compiler is able to determine what
the right type of an arg is at a function call (ie: forward declararions)
then '0' will serve the purpose of the "magic object" quite well
enough, as it does in assignments to pointers, and comparisons.

Robert Elz
decvax!mulga!kre

dan@ttds.UUCP (Dan Sahlin) (02/09/84)

Since I started this NULL vs. 0 discussion, I perhaps should repeat
my reasons for wanting people use to NULL instead of 0.

+ 0 is 16 bits on my machine.
+ NULL is declared as (char *)0 on my machine which makes 32 bits.
+ Programs that use 0 as pointer *crash*.
+ Programs that use NULL *don't crash*.
  The reasons (although very obvious) have been very well elaborated
  earlier so I will not repeat them.

While hoping to end this NULL discussion soon, I still have a question:
Is there really a C-compiler that makes a difference between a "(char *)0"
and "(int *)0" so a program can *crash* if it uses the wrong one?

	Dan Sahlin      ( ..mcvax!enea!ttds!dan )

jr@qtlon.UUCP (02/18/84)

The system V manual says that NULL is 0.
-- 
									++jim
					<england>!ukc!qtlon!jr		++jim