[net.lang.c] NULL pointer

davidsen@steinmetz.UUCP (Davidsen) (11/15/85)

I expressed the following opinion at some of the early meetings of X3J11, I'll
repeat it here:

NULL being an arithmetic value rather than a pointer results in conversion
problems, special cases, etc in many cases. This can result in "portable"
programs failing on some machines, particularly those which on which all
pointers are not the same (either size or form) such as Honeywell, Buroughs,
and Cray (at least the Cray2).

Many of these problems would not exist today if the original definition of
NULL had been:

	#define NULL	((char *) 0)

rather than arithmetic zero. This would make it work on machines which use a
non-zero null pointer. I have tried doing this in a number of programs to
examine the effect on lint and/or the generated code, and have yet to find a
case where it caused a problem (although it did reveal a few genuine bugs in
"working code").

Perhaps there are cases in which this construct would cause a problem, and I
would like to hear any technical comments, particularly to those still on
X3J11 and/or early UNIX implementors.
-- 
	billD	(..seismo!rochester!steinmetz!crdos1!davidsen)
		(davidsen@GE-CRD.ARPA)

"It seemed like a good idea at the time..."

ron@brl-sem.ARPA (Ron Natalie <ron>) (11/18/85)

> 
> Many of these problems would not exist today if the original definition of
> NULL had been:
> 
> 	#define NULL	((char *) 0)
> 
Personally, I never use NULL.  NULL is a misnomer.  Zero is defined
to be the invalid pointer, zero should be used.

But we've been through this before.  Assigning a type to NULL opens
up as many problems as it solves.  There are a class of machines where
byte pointers themselves are different than pointers to any other type.

If you are going to assing a type to NULL you probably ought to use
(void *).

=Ron

bilbo.niket@locus.ucla.edu (Niket K. Patwardhan) (11/19/85)

>Date:     Sun, 17 Nov 85 3:59:50 EST
>From:     Doug Gwyn (VLD/VMB) <gwyn@BRL.ARPA>
>To:       Davidsen <davidsen%steinmetz.uucp@BRL.ARPA>
>cc:       info-c@brl-tgr.arpa
>Subject:  Re:  NULL pointer
>
>I'll see if I can beat Guy Harris to this one:
>
>The whole topic of the correct #definition for NULL
>has been beat to death more than once on this mailing list.
>
>The answer is that NULL must be defined as simply 0
>and that code that misuses NULL cannot be fixed in
>general by any change in its #definition.
>
>Consider
>
>	hi_func()
>	{
>		...
>		lo_func( ..., NULL, ... );
>		...
>	}
>
>	lo_func( ..., p, ... )
>		int *p;
>	{
>		...
>	}
>
>This code is INCORRECT, whether NULL is #defined as 0
>or as (char *)0.  The only correct way to do this
>(not counting the new X3J11 parameter type coercion
>mechanism) is to change the invocation of lo_func to:
>
>		lo_func( ..., (int *)NULL, ... );
>
>By trying to #define NULL as (char *)0, you are trying
>to keep the programmer from having to understand this
>and deal with it properly.  You also cause problems
>with statements such as
>
>	if ( p == NULL )
>		do_stuff;
>
>when the type of `p' is not (char *).  The only
>universal #definition for NULL that works in all such
>cases is 0.  (Of course, some machines allow sloppier
>usage.  This discussion assumes universality.)
>
>#define	NULL	0	/* don't muck around with it */

The last line: ---- I agree with it whole-heartedly.

But..... for a machine with non-zero null pointer representations

>		lo_func( ..., (int *)NULL, ... );

still doesn't work. What the callee routine gets is a pointer with an all-zero
representation.... which isn't a null pointer. The ONLY thing that does work is

		lo_func( ..., p=0, ....);

where p is a pointer of the requisite type! Of course, with parameter type
coercion (or should I say conversion.. it isn't the same thing), this
shouldn't be necessary!

alexis@reed.UUCP (Alexis Dimitriadis) (11/19/85)

> 	#define NULL	((char *) 0)
> 
> rather than arithmetic zero. This would make it work on machines which use a
> non-zero null pointer. I have tried doing this in a number of programs to
> examine the effect on lint and/or the generated code, and have yet to find a
> case where it caused a problem (although it did reveal a few genuine bugs in
> "working code").

What exactly did you try?  Defining NULL this way generates _compiler_
errors whenever you use NULL in a context that requires anything other
than a char pointer, e.g. a comparison.  The only case when it _fixes_
things is when NULL is a function argument, and the draft standard will
provide for function argument coercion, as well.  Example:

#include <stdio.h>
#undef NULL
#define NULL ((char *)0)
main()
{
	if (fopen("foo", "w") == NULL)
		;
}
% cc t.c
"t.c", line 8: warning: illegal pointer combination

--Alexis Dimitriadis
-- 
_______________________________________________
  As soon as I get a full time job, the opinions expressed above
will attach themselves to my employer, who will never be rid of
them again.
				alexis @ reed
    {decvax,ihnp4,ucbcad,uw-beaver}!tektronix!reed.UUCP

ron@brl-sem.ARPA (Ron Natalie <ron>) (11/19/85)

> How many times do people have to go over this? Perhaps the problem is that
> people don't understand what a cast does!
> 
> A cast does NOT change the bit pattern! As a matter of fact, it is a means
> of preventing a conversion that DOES change the bit pattern! A cast simply
> changes the PRESUMED type of a stored bit-pattern, without changing the
> pattern itself.  (The only exception is extension or truncation to match
> the size of the destination type. When extending you can use any fill bits you
> consider reasonable; the uSOFT C compiler inserts the data segment number when
> converting near pointers types to far pointer types). If the resulting type
> still does not match the target, then a conversion occurs DURING THE ASSIGNMENT!

I don't know how many times you have been over it, but casts do change the
bit pattern of what they apply to, if necessary.
Let's take out our copies of Kernighan and Ritchie, turn to page 42 and
read aloud:

	"The precise meaning of cast is in fact as if expression were
	 assigned to a variable of the specified type, which is then
	 used in place of the whole construction"

Consider the following:

	float	f1, f2;

	union  {
		float	union_float;
		int	union_int;
	} samebits;
	
	main()  {

		f1 = (float) -1;
		samebits.union_int = -1;
		f2 = samebits.union_float;

		printf("f1 = %f, union_float = %f, f2 = %f\n",
			 f1, samebits.union_float);
	}

If casts worked you way, I would assign all ones to the data area taken
up by f1.  No conversion from int would occur because I have now changed
the type to float and the assignment sees that the left side and the
right side now have the same type.  Just to check the computation the
other way, I set up a union that guarantees that the float and the int
will be colocated and no automatic type conversion of any kind will occur.
What happens when we run this on an ANSI conforming C compiler

	f1 = -1.000000, union_float = 0.000000, f2 = 0.000000

Surprise, type casts change the data.  Under your scheme, how do you
deal with a cast to something with a different size than the original object?

> 
> The ONLY totally correct thing to do if machines with non-zero nulls
> are involved is to assign 0 to a pointer variable, and then pass the variable.
> So if you want to be totally correct you would define NULL like this:-
> 
> #define NULL	(___p=0)
> static char *___p;

This would be exactly the same as casting 0 to (char *) without the
side effect of loading ___p, BY DEFINITION.

CASTS do not change the value of the thing to the right of the expression,
but they will change the MACHINE REPRESENTATION to that of the new type.
If you worked on machines like I've done that have 5 different types of
pointer (character, word, half-word, quarter-word, and function)
representation, you'd appreciate the way cast works.

-Ron

gwyn@brl-tgr.ARPA (Doug Gwyn <gwyn>) (11/19/85)

> But..... for a machine with non-zero null pointer representations
>
> >		lo_func( ..., (int *)NULL, ... );
>
> still doesn't work. What the callee routine gets is a pointer with an all-zero
> representation.... which isn't a null pointer. The ONLY thing that does work is
> 
> 		lo_func( ..., p=0, ....);
> where p is a pointer of the requisite type!  ...

and

> How many times do people have to go over this? Perhaps the problem is that
> people don't understand what a cast does!
> ...

Ron Natalie has already replied restrainedly (for him) to this fool's
posting, so I will simply FLAME HIM for CONFUSING THE POOR NOVICE by
providing MISINFORMATION about what C type casts do.  Dennis Ritchie
about a year ago made one of his rare appearances in this newsgroup
to set this matter straight, and it is covered quite well in all the
standard references for C.

A type cast has the same semantics as assignment to a temporary
variable of the same type as the cast and use of that variable
thereafter in the embedding context.  In particular, this means
that (gleep *)0 is a proper null pointer to a thing of type `gleep';
this is because assignment of the integer constant 0 to a pointer
has those semantics.  There is no assumption whatsoever about "bit
patterns" in any of this!

I try to be polite (for me) in these postings, but this yo-yo has
done a disservice to those readers who do not already know the C
language quite well and who might therefore think that he knows
what he's talking about.

To repeat the CORRECT information that I originally posted, so that
the novice is not left with any misconceptions on the matter:

If a function is declared

	func( ..., foo, ... );
		int *foo;

then invoking it as either

	func( ..., 0, ... );

or

	func( ..., (char *)0, ... );

is simply WRONG, and only works (by accident) on some implementations.
The correct way to pass a null pointer to this function is

	func( ..., (int *)0, ... );

No #definition of NULL (as (char *)0, etc.) can in general keep the
programmer from having to provide this explicit type cast when
passing NULL as an argument to a function.

(In X3J11-conforming implementations, the coercion will be done
automatically if a function prototype declaration is in scope,
but that is a separate matter from the original discussion.)

bilbo.niket@locus.ucla.edu (Niket K. Patwardhan) (11/20/85)

OH HELL!  I made a serious boo-boo. Thanks Ron for not flaming me, but simply
quoting from K&R. And if Dennis Ritchie did come on the network and clear it
up to be K&R, then I can't even argue for a K&R error (it does happen!).

BTW, I have had this misconception ever since I heard of casts and apparently
a number of people have also at least started out with such a notion. Maybe it
is a good thing for us to be spouting forth on the network... occasionally
once in a while we learn something where we think we know it even backwards.

henry@utzoo.UUCP (Henry Spencer) (11/20/85)

> But..... for a machine with non-zero null pointer representations
> >		lo_func( ..., (int *)NULL, ... );
> still doesn't work. What the callee routine gets is a pointer with an all-zero
> representation.... which isn't a null pointer. The ONLY thing that does work is
> 		lo_func( ..., p=0, ....);
> where p is a pointer of the requisite type!...

Where on Earth did you get that idea?  (Actually, I know:  from an obsolete
writeup and/or compiler.)  The semantics of casts were somewhat obscure when
they were first introduced, but the eventual rigorous definition was "a cast
acts just like an assignment to a variable of that type".
-- 
				Henry Spencer @ U of Toronto Zoology
				{allegra,ihnp4,linus,decvax}!utzoo!henry

fnf@unisoft.UUCP (11/22/85)

In article <3423@brl-tgr.ARPA> bilbo.niket@locus.ucla.edu (Niket K. Patwardhan) writes:
>BTW, I have had this misconception ever since I heard of casts and apparently
>a number of people have also at least started out with such a notion.

Did you by chance get your first exposure to C on a PDP-11 using the DECUS
C compiler?  I did, and it repeatedly stated throughout the documentation 
that "casts were not conversions", and that casting something to another
type was the same as treating the old object's *bit-pattern* as if
it was the new type with the same *bit-pattern*.  I don't know if it still
claims this, as this was several years ago.

-Fred

peter@graffiti.UUCP (Peter da Silva) (11/24/85)

> that "casts were not conversions", and that casting something to another
> type was the same as treating the old object's *bit-pattern* as if
> it was the new type with the same *bit-pattern*.  I don't know if it still
> claims this, as this was several years ago.

Last I heard (and I may be wrong: someone ripped off my K&R), (int)1.0 had
the value 1, not 0104000000000 or whatever.  The DECUS 'C' compiler also
had a broken floating point package, and I wonder if that's related.
-- 
Name: Peter da Silva
Graphic: `-_-'
UUCP: ...!shell!{graffiti,baylor}!peter
IAEF: ...!kitty!baylor!peter

bilbo.niket@locus.ucla.edu (Niket K. Patwardhan) (11/25/85)

>In article <3423@brl-tgr.ARPA> bilbo.niket@locus.ucla.edu (Niket K. Patwardhan) writes:
>>BTW, I have had this misconception ever since I heard of casts and apparently
>>a number of people have also at least started out with such a notion.
>
>Did you by chance get your first exposure to C on a PDP-11 using the DECUS
>C compiler?

Yup: and I started on it in 1978. For a while there I thought I was losing it.