[comp.arch] NULL pointers

guy@gorodish.Sun.COM (Guy Harris) (06/08/88)

(The preceding article's contents represent a misunderstanding of the C
language, rather than a valid statement of what an architecture should or
should not have; I've therefore redirected followups to "comp.lang.c", although
many readers there are probably tired of seeing these same
misunderstandings....)

> This presents a sequential search through a linear list more positively than
> 
> 	#include <stdio.h>	/*stupid place for NULL*/
> 	...
> 	Foobar *foobar;
> 	for (foobar = first; foobar != (Foobar*)NULL; foobar = foobar->next)
> 	{
> 	}
> 
> (For code to be truly portable, NULL must be cast to the appropriate pointer
> type every time it is used since it is nothing more than an integer bit
> pattern :-)

If the smiley really means "I don't believe the statement that precedes the
smiley", OK; however, the statement in question is completely false.  Any valid
C compiler will turn

	foobar != 0

into a comparison of "foobar" with the appropriate representation for a null
pointer of type "pointer to Foobar".

> Why not just adopt a pattern of all zeroes as NULL instead?  Just ensure
> that this convention is observed by your malloc.  Then we can write
> 
> 	if (func)
> 		result = (*func)();
> 
> rather than using double negatives:
> 
> 	if (func != (int(*)(void))NULL)
> 		result = (*func)();

"if (func != (int(*)(void))NULL)" is equivalent to "if (func != NULL)", because
the compiler will perform the type conversion.  "if (func != NULL)" is
equivalent to "if (func != 0)", because "NULL" is supposed to be defined as "0"
(or "(void *)0", but the latter isn't really necessary).  "if (func != 0)" is
equivalent to "if (func)".  Therefore, you can write "if (func)" instead of
"if (func != (int(*)(void))NULL)" *regardless* of the representation of a null
pointer.

The confusion on this point probably results because C did not have function
prototypes until some compiler vendors put them in in anticipation of ANSI C.
Thus, while the compiler has enough information to know that NULL (or 0, it's
the same thing) in "foobar = NULL" or "if (func != NULL)" should be converted
to the appropriate pointer type, the compiler lacks enough information to do
the type conversion in "setbuf(stdout, NULL)" - it doesn't know that the second
argument to "setbuf" is "char *".  Therefore, you have to tell the compiler to
perform this conversion by explicitly casting the pointer:
"setbuf(stdout, (char *)NULL)".

If you have function prototypes in your C implementation, and have included the
proper include files or have otherwise arranged that prototypes are in scope
for all functions to which you refer, the compiler can do the conversion:

	void setbuf(FILE *stream, char *buf);

	...

	setbuf(stdout, NULL);

Please don't post a followup to this article unless you have good evidence that
the C *language* doesn't specify that the appropriate conversions be done in
the examples given.  Thank you.

jeffa@hpmwtla.HP.COM (Jeff Aguilera) (06/18/88)

ANSI C is not C.  Prototypes do not exist in C.  Please show me where in
K&R that it states that "0" refers to the NULL pointer irrespective of the
underlying implementation.  Mr. Wells, if "0" does refer to the null
pointer, why do some systems have #define NULL (-1) in stdio?  My 
statement regarding casting is correct, since not all pointers need be 
of the same size.  Prototypes eliminate this annoyance, but I live with
a compiler void of prototypes :-(

Jeff "still wondering when C will be as clean as Algol-68" Aguilera

jgk@speech2.cs.cmu.edu (Joe Keane) (06/20/88)

In article <3100003@hpmwtla.HP.COM> jeffa@hpmwtla.HP.COM (Jeff Aguilera) writes:
>ANSI C is not C.  Prototypes do not exist in C.  Please show me where in
>K&R that it states that "0" refers to the NULL pointer irrespective of the
>underlying implementation.  Mr. Wells, if "0" does refer to the null
>pointer, why do some systems have #define NULL (-1) in stdio?  My 
>statement regarding casting is correct, since not all pointers need be 
>of the same size.  Prototypes eliminate this annoyance, but I live with
>a compiler void of prototypes :-(

Please, not this again!  Any system with `#define NULL (-1)' is so
broken it's pitiful.  Code in my previous posting must work right.

>Jeff "still wondering when C will be as clean as Algol-68" Aguilera
--Joe "enough about NULL" Keane

darrylo@hpsrli.HP.COM (Darryl Okahata) (06/20/88)

In comp.arch, jeffa@hpmwtla.HP.COM (Jeff Aguilera) writes:

> ANSI C is not C.  Prototypes do not exist in C.  Please show me where in
> K&R that it states that "0" refers to the NULL pointer irrespective of the

Pages 97-98 of K&R, first edition:

     "We write NULL instead of zero, however, to indicate more clearly that
     this is a special value for a pointer.  In general, integers cannot
     meaningfully be assigned to pointer; zero is a special case."

For the second edition of K&R ("ANSI C"), a passage similar to the above
appears somewhere around page 102.

> underlying implementation.  Mr. Wells, if "0" does refer to the null
> pointer, why do some systems have #define NULL (-1) in stdio?  My 

     Personally, I think that ANY "C" compiler that does not assign 0 (or even
0L - yuk) to NULL is busted.  Using a compiler that defines NULL as (-1), try
porting any program that appears in comp.sources.*.  You'll find that it's a
nightmare, as almost ALL C programs make the assumption that NULL is 0.

     [ ... ]
> Jeff "still wondering when C will be as clean as Algol-68" Aguilera

     OK, this topic has been brought up for the N-th to the i-th time.  Time to
kill it.

     -- Darryl Okahata
	{hplabs!hpccc!, hpfcla!} hpsrla!darrylo
	CompuServe: 75206,3074

Disclaimer: the above is the author's personal opinion and is not the
opinion or policy of his employer or of the little green men that
have been following him all day.

hoefling@uicsrd.csrd.uiuc.edu (06/21/88)

"The C Programming Language", Kernighan and Ritchie, 1978, page 192:

Appendix A, section 7.14 "Assignment operators":

"However, it is guaranteed that assignment of the constant 0 to a pointer
will produce a null pointer distinguishable from a pointer to any object."

guy@gorodish.Sun.COM (Guy Harris) (06/21/88)

> ANSI C is not C.  Prototypes do not exist in C.  Please show me where in
> K&R that it states that "0" refers to the NULL pointer irrespective of the
> underlying implementation.

7.7 Equality operators

	...

	A pointer may be compared to an integer, but the result is machine
	dependent unless the integer is the constant 0.  A pointer to which 0
	has been assigned is guaranteed not to point to any object, and will
	appear to be 0; in conventional usage, such a pointer is considered to
	be null.

7.14 Assignment operators

	...

	However, it is guaranteed that assignment of the constant 0 to a
	pointer will produce a null pointer distinguishable from a pointer to
	any object.

Next question.

> ...if "0" does refer to the null pointer, why do some systems have #define
> NULL (-1) in stdio?

Because the implementors were ignorant.  Any language implementation that
defines NULL as -1, and purports to be an implementation of C, is broken.

Next question.

> My statement regarding casting is correct, since not all pointers need be 
> of the same size.  Prototypes eliminate this annoyance, but I live with
> a compiler void of prototypes :-(

Your statement regarding casting was:

> 	#include <stdio.h>	/*stupid place for NULL*/
> 	...
> 	Foobar *foobar;
> 	for (foobar = first; foobar != (Foobar*)NULL; foobar = foobar->next)
> 	{
> 	}

> (For code to be truly portable, NULL must be cast to the appropriate pointer
> type every time it is used since it is nothing more than an integer bit
> pattern :-)

which is absolutely, positively, INcorrect.  The cast is totally unnecessary in
the "for" loop in question; the compiler is quite capable of figuring out that
0 or NULL must be converted to a null pointer of type "Foobar *" before
comparing it, and all valid C compilers will do so (if you know of a compiler
that does not, it is invalid - period).

Furthermore, as there are no function calls whatsoever in your example,
prototypes have no effect.

The ONLY place where you are required to cast NULL properly is when passing
arguments to a procedure; prototypes eliminate that unless you have a function
(such as "execl") that takes a variable number of arguments or otherwise cannot
have its calling sequence fully described by a prototype.  Any valid compiler
will perform the required conversion automatically in all other cases
(assignments and comparisons, as described above).

scott@award.UUCP (Scott Smith) (06/22/88)

In article <3100003@hpmwtla.HP.COM> jeffa@hpmwtla.HP.COM (Jeff Aguilera) writes:
>ANSI C is not C.  Prototypes do not exist in C.  Please show me where in
>K&R that it states that "0" refers to the NULL pointer irrespective of the
>pointer, why do some systems have #define NULL (-1) in stdio? 

Actually K&R does, p97: "C guarantees that no pointer that validly points
at data will contain zero, so a return value of zero can be used to signal
an abnormal event... We write NULL instead of zero, however, to indicate
more clearly that this is a special value for a pointer."

The reason some systems have #define NULL (-1) in stdio is becuase zero
*can* be a pointer to valid data (as is the case in my micro). In this
case, NULL is simply changed to be a value that can't be a valid pointer
on that particular system.

						S. Smith 

chris@mimsy.UUCP (Chris Torek) (07/01/88)

>In article <3100003@hpmwtla.HP.COM> jeffa@hpmwtla.HP.COM (Jeff Aguilera)
asks:
>>why do some systems have #define NULL (-1) in stdio? 

In article <743@award.UUCP> scott@award.UUCP (Scott Smith) answers:
>K&R p97: "C guarantees that no pointer that validly points
>at data will contain zero" ...

(A better place is the appendix, which is more explicit.)

>The reason some systems have #define NULL (-1) in stdio is becuase zero
>*can* be a pointer to valid data (as is the case in my micro). In this
>case, NULL is simply changed to be a value that can't be a valid pointer
>on that particular system.

Or, to simplify it:

	Some systems have `#define NULL (-1)' in <stdio.h> because
	some systems are broken.

If location zero is a valid data address, the compiler must take
care to ensure that either nil pointers are not the all-zero bit
pattern, or that something which is never accessed from C is stored
in location zero.

Given the C code

	main()
	{
		char *p = 0;

		if (p) *p = 0;
	}

the following pseudo-assembly is legal and correct:

	main_::
		create_frame

		move	#0xff00,-4(stack)	| p = (char *)0

		move	-4(stack),r0
		cmp	r0,#0xff00		| if (p)
		branch	notequal,$1
		move	#0,0(r0)		| *p = 0
	$1:

		destroy_frame

		return

Notice how pointer assignments and comparisons with `0' turn into
assignments and comparisons with a magic nil pointer.  Whether that
nil pointer's value is in fact zero is not determined, but IN THE
SOURCE LANGUAGE THAT NIL POINTER IS WRITTEN AS IF IT WERE THE INTEGER
ZERO.  (The dpANS also allows (void *)0.)

-----------------------------------------------------------------------
In C, NULL may or may not be in fact zero, but it is *written* as zero.
-----------------------------------------------------------------------

(Nil pointers also have types, and cannot be correctly discussed without
also mentioning their types, but this is the important part.)
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

guy@gorodish.Sun.COM (Guy Harris) (07/01/88)

> The reason some systems have #define NULL (-1) in stdio is becuase zero
> *can* be a pointer to valid data (as is the case in my micro). In this
> case, NULL is simply changed to be a value that can't be a valid pointer
> on that particular system.

Only if the implementor was ignorant, in which case they shouldn't be trying to
implement C without having somebody around who *does* know the language.

You don't have to guarantee that that location is inaccessible, you merely have
to guarantee that no object that a C program can refer to is at that address.
If "malloc" will never return a pointer to an object at location 0, and if no
static or global data or code defined in a C program will ever be put at
location 0, and if no data object on the stack will be at location 0, this is
sufficient.

If it is truly impossible to arrange for this to happen, what you can do is:

	1) define the format of a null pointer to be, say, 0xFFFF;

	2) have the C compiler properly handle null pointers.

Thus, the C code

	char *p;

	p = 0;

would generate machine code something like

	move.i	#ffff,p

(i.e., store the bit pattern 0xFFFF into "p"), and the C code

	char *p;

	if (p == 0)

or

	char *p;

	if (!p)

would generate machine code something like

	cmp.i	#ffff,p
	bne	1f
	(code inside "if")
1:

(i.e., compare "p" against the bit pattern 0xFFFF; if it's equal, the pointer
is "equal to zero", or null).

Given that, NULL should be defined as 0, not -1.

bsy@PLAY.MACH.CS.CMU.EDU (Bennet Yee) (09/28/88)

....
blithering about universal intermediate languages and cost for checking
every pointer reference to enforce *NULL == NULL
....

Of course, if your machine core dumps on dereferencing NULL, just trap the
fault (SIGSEGV), check the instruction that caused the trap, and patch up
and return if it was due to dereferencing NULL; otherwise dump core.  If we
presume that most pointers dereferenced are not NULL, then the run-time
costs are small.  It's a very machine dependent solution, but certainly can
be done on any Unix machine.

-bsy
-- 
Internet:	bsy@cs.cmu.edu		Bitnet:	bsy%cs.cmu.edu%smtp@interbit
CSnet:	bsy%cs.cmu.edu@relay.cs.net	Uucp:	...!seismo!cs.cmu.edu!bsy
USPS:	Bennet Yee, CS Dept, CMU, Pittsburgh, PA 15213-3890
Voice:	(412) 268-7571

blarson@skat.usc.edu (Bob Larson) (09/28/88)

In article <3112@pt.cs.cmu.edu> bsy@PLAY.MACH.CS.CMU.EDU (Bennet Yee) writes:
>....
>blithering about universal intermediate languages and cost for checking
>every pointer reference to enforce *NULL == NULL
>....
>
>Of course, if your machine core dumps on dereferencing NULL, just trap the
>fault (SIGSEGV), check the instruction that caused the trap, and patch up
>and return if it was due to dereferencing NULL; otherwise dump core.

This is a possible solition to running software that assumes NULL can be
dereferenced on a machine that doesn't allow this, but it still leaves
the problem of getting software that assumes that a NULL pointer dereference
WILL cause an exception to run on machine that does allow NULL to be
dereferenced.  (Obviously, with much overhead, it can be checked at
every pointer dereference.)
-- 
Bob Larson	Arpa: Blarson@Ecla.Usc.Edu	blarson@skat.usc.edu
Uucp: {sdcrdcf,cit-vax}!oberon!skat!blarson
Prime mailing list:	info-prime-request%ais1@ecla.usc.edu
			oberon!ais1!info-prime-request

chip@ateng.ateng.com (Chip Salzenberg) (09/30/88)

According to bsy@PLAY.MACH.CS.CMU.EDU (Bennet Yee):
>Of course, if your machine core dumps on dereferencing NULL, just trap the
>fault (SIGSEGV), check the instruction that caused the trap, and patch up
>and return if it was due to dereferencing NULL; otherwise dump core.

It is not not always possible to restart faulted instructions.  The 68000,
for example, is not capable of restarting an instruction after a bus fault.
This problem has been fixed in the 68010 and all its successors; but if
an intermediate language doesn't work on the 68000, it can hardly be called
"universal".
-- 
Chip Salzenberg                <chip@ateng.uu.net> or <uunet!ateng!chip>
A T Engineering                My employer may or may not agree with me.
	  The urgent leaves no time for the important.

thomas@uplog.se (Thomas Hameenaho) (10/03/88)

In article <3112@pt.cs.cmu.edu> bsy@PLAY.MACH.CS.CMU.EDU (Bennet Yee) writes:
>Of course, if your machine core dumps on dereferencing NULL, just trap the
>fault (SIGSEGV), check the instruction that caused the trap, and patch up
>and return if it was due to dereferencing NULL; otherwise dump core.  If we
>presume that most pointers dereferenced are not NULL, then the run-time
>costs are small.  It's a very machine dependent solution, but certainly can
>be done on any Unix machine.

This problem should be easy to solve hadn't it been the case that many
Unix kernels are defunct when it comes to using the a.out header.
Our Pyramid (hint hint!) dumps core if the text segment doesn't start
on virtual address 0! The sysV 68020 machines we have can correctly
use the header. You can for instance have the entry point at address 4
and have a 0 at address 0 if you just want to be able to run the code as is.
Or if you prefer you can skip page 0 and trap any references to 0 and
once and for all fix the offending code.

-- 
Real life:	Thomas Hameenaho		Email:	thomas@uplog.{se,uucp}
Snail mail:	TeleLOGIC Uppsala AB		Phone:	+46 18 189406
		Box 1218			Fax:	+46 18 132039
		S - 751 42 Uppsala, Sweden

peter@ficc.uu.net (Peter da Silva) (10/03/88)

In article <1988Sep29.141726.14882@ateng.ateng.com>, chip@ateng.ateng.com (Chip Salzenberg) writes:
> According to bsy@PLAY.MACH.CS.CMU.EDU (Bennet Yee):
> >Of course, if your machine core dumps on dereferencing NULL, just trap the
> >fault (SIGSEGV), check the instruction that caused the trap, and patch up
> >and return if it was due to dereferencing NULL; otherwise dump core.

No no, no. If the program traps on dereferencing NULL you pack it up and send
it back to the vendor. This is a *bug*. Even if it's safe on some machines
it's still a bug.

And for the other case, I can't imagine anyone actually writing code that
depended on *NULL being illegal...
-- 
Peter da Silva  `-_-'  Ferranti International Controls Corporation.
"Have you hugged  U  your wolf today?"            peter@ficc.uu.net

radford@.ucalgary.ca (Radford Neal) (10/04/88)

In article <3112@pt.cs.cmu.edu>, bsy@PLAY.MACH.CS.CMU.EDU (Bennet Yee) writes:

> .....
> blithering about universal intermediate languages and cost for checking
> every pointer reference to enforce *NULL == NULL
> .....
> 
> ... if your machine core dumps on dereferencing NULL, just trap the
> fault ... patch up ... return. It's a very machine dependent solution, 
> but certainly can be done on any Unix machine.

Really? It can't be done on several models of the PDP-11. Models that 
supported UNIX. 

I suspect it can't be done on many UNIX systems that don't support
demand paging. Maybe not even on some that do.

    Radford Neal