[comp.lang.c] Why NULL is 0

chris@mimsy.UUCP (Chris Torek) (03/09/88)

(You may wish to save this, keeping it handy to show to anyone who
claims `#define NULL 0 is wrong, it should be #define NULL <xyzzy>'.
I intend to do so, at any rate.)

Let us begin by postulating the existence of a machine and a compiler
for that machine.  This machine, which I will call a `Prime', or
sometimes `PR1ME', for obscure reasons such as the fact that it
exists, has two kinds of pointers.  `Character pointers', or objects
of type (char *), are 48 bits wide.  All other pointers, such as
(int *) and (double *), are 32 bits wide.

Now suppose we have the following C code:

 	main()
	{
 		f1(NULL);	/* wrong */
 		f2(NULL);	/* wrong */
 		exit(0);
 	}
 
 	f1(cp) char *cp; { if (cp != NULL) *cp = 'a'; }
 	f2(dp) double *dp; { if (dp != NULL) *dp = 2.2; }

There are two lines marked `wrong'.  Now suppose we were to define NULL
as 0.  Clearly both calls are then wrong: both pass `(int)0', when the
first should be a 48 bit (char *) nil pointer and the second a 32 bit
(double *) nil pointer.

Someone claims we can fix that by defining NULL as (char *)0.  Suppose
we do.  Then the first call is correct, but the second now passes a
48 bit (char *) nil pointer instead of a 32 bit (double *) nil pointer.
So much for that solution.

Ah, I hear another.  We should define NULL as (void *)0.  Suppose we
do.  Then at least one call is not correct, because one should pass
a 32 bit value and one a 48 bit value.  If (void *) is 48 bits, the
second is wrong; if it is 32 bits, the first is wrong.

Obviously there is no solution.  Or is there?  Suppose we change
the calls themselves, rather than the definition of NULL:

	main()
	{
		f1((char *)0);
		f2((double *)0);
		exit(0);
	}

Now both calls are correct, because the first passes a 48 bit (char *)
nil pointer, and the second a 32 bit (double *) nil pointer.  And
if we define NULL with

	#define NULL 0

we can then replace the two `0's with `NULL's:

	main()
	{
		f1((char *)NULL);
		f2((double *)NULL);
		exit(0);
	}

The preprocessor changes both NULLs to 0s, and the code remains
correct.

On a machine such as the hypothetical `Prime', there is no single
definition of NULL that will make uncasted, un-prototyped arguments
correct in all cases.  The C language provides a reasonable means
of making the arguments correct, but it is not via `#define'.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

g-rh@cca.CCA.COM (Richard Harter) (03/10/88)

Just a small addendum to Chris's excellent summary -- the current generation
of PR1ME C compilers use a 48 bit size for all pointers; but earlier versions
used 48 bits for char pointers and 36 for everything else.  I don't know if
all formats are the same -- but then I don't need to know that.  All I need
to know is that everything works properly if I cast all of my pointers right.

And this really works!  Recently I did a PRIMOS upgrade of our software.
I took 40,000 lines of C, developed and maintained, under UNIX, and ported
it to PRIMOS with 0 pointer problems.  [There are sundry incompatibilities in
library routine calls, another matter.]

Long live portable software.
-- 

In the fields of Hell where the grass grows high
Are the graves of dreams allowed to die.
	Richard Harter, SMDS  Inc.

pablo@polygen.uucp (Pablo Halpern) (03/11/88)

From article <10576@mimsy.UUCP>, by chris@mimsy.UUCP (Chris Torek):
> (You may wish to save this, keeping it handy to show to anyone who
> claims `#define NULL 0 is wrong, it should be #define NULL <xyzzy>'.
> I intend to do so, at any rate.)
> 
> Let us begin by postulating the existence of a machine and a compiler
> for that machine.  This machine, which I will call a `Prime', or
> sometimes `PR1ME', for obscure reasons such as the fact that it
> exists, has two kinds of pointers.  `Character pointers', or objects
> of type (char *), are 48 bits wide.  All other pointers, such as
> (int *) and (double *), are 32 bits wide.

I must admit, you have come up with a situation where the uncasted
value NULL would be an incorrect parameter to a function that didn't
have a prototype regardless of the definition of NULL. (If you missed
the original posting, please don't flame about this.  Ask someone to
mail the original to you.)  In the situation mentioned, the compiler
could not know how wide to make the null pointer when passing it to the
function.  Indeed, even with NULL defined as (void *) 0, dpANS does not
say that the size of a (void *) is the same as the size of any other
pointer.

However, if I were writing a C compiler, I would choose a size for all
pointers equal to the size of the largest possible pointer.  This would
allow code that passed uncasted NULL to work correctly, provided NULL
is a type as large as a pointer.  This is not because dpANS says it
should be so, but because so much code would break if it were not.
Perhaps ANSI should add the restriction that all pointer types must be
the same size in an effort to "codify common existing practice."

gwyn@brl-smoke.ARPA (Doug Gwyn ) (03/12/88)

In article <124@polygen.UUCP> pablo@polygen.uucp (Pablo Halpern) writes:
>Perhaps ANSI should add the restriction that all pointer types must be
>the same size in an effort to "codify common existing practice."

But it isn't existing practice.

If you mean, non-portable, buggy code is existing practice, well yes,
but so what?  That certainly shouldn't serve as the basis for a standard.

pardo@june.cs.washington.edu (David Keppel) (03/12/88)

In article <124@polygen.UUCP> pablo@polygen.uucp (Pablo Halpern) writes:
>However, if I were writing a C compiler, I would choose a size for all
>pointers equal to the size of the largest possible pointer.  This would
>allow code that passed uncasted NULL to work correctly, provided NULL
>is a type as large as a pointer.  This is not because dpANS says it
>should be so, but because so much code would break if it were not.
>Perhaps ANSI should add the restriction that all pointer types must be
>the same size in an effort to "codify common existing practice."

My guess is that you would want to do this only if you didn't care
whether your compiler produced efficient code or made "reasonable"
time/space efficiency tradeoffs.

I know we've gone over this a lot, and perhaps the topic should be
redirected to comp.arch or some such, but (if nobody minds too much)
could somebody who understands please tell me what kind of efficiency
you lose (in runtime, codespace, and dataspace) in trying to make
everything the most general pointer type (on existing architectures)?

I'd sure like to know.

 ;-D on  (Well it looked like luminous phosphor when I wrote it)  Pardo

louie@trantor.umd.edu (Louis A. Mamakos) (03/12/88)

In article <124@polygen.UUCP> pablo@polygen.uucp (Pablo Halpern) writes:
>However, if I were writing a C compiler, I would choose a size for all
>pointers equal to the size of the largest possible pointer.  

Please don't do this.  On, for example, our Unisys 1100 machine, a "regular"
pointer is 8 bytes long (2 words, 72 bits).  A pointer to a function is 
64 bytes long (8 words, 288 bits).  Yes, this is a word-addressable machine
with 4 9-bit bytes per word. The existing semantics work just fine if
you don't assume programmer brain-damage.

Louis A. Mamakos  WA3YMH    Internet: louie@TRANTOR.UMD.EDU
University of Maryland, Computer Science Center - Systems Programming

friedl@vsi.UUCP (Stephen J. Friedl) (03/13/88)

In article <124@polygen.UUCP>, pablo@polygen.uucp (Pablo Halpern) writes:
> However, if I were writing a C compiler, I would choose a size for all
> pointers equal to the size of the largest possible pointer.  This would
> allow code that passed uncasted NULL to work correctly, provided NULL
> is a type as large as a pointer.  This is not because dpANS says it
> should be so, but because so much code would break if it were not.
> Perhaps ANSI should add the restriction that all pointer types must be
> the same size in an effort to "codify common existing practice."

I think this is naive.  Presumably, the large "common" pointer
format would pass around the machine OK but it still must be
converted to the machine's native pointer types to actually
use -- how efficiently will this be done?  This might be like the 80x86
huge pointer calculations or the old promote-float-to-double
rules -- they makes life a little easier for the (lazy?)
programmer at the larger expense in time and compiler complexity

Perhaps more reasonable is to promote all pointers to the same
large width while passing them on the stack and convert them back
on the other end: this would fix the alignment issues but would
still slow things down.

I don't favor this approach but I bring it up in the spirit of
this discussion.  The obvious answer is to be very rigorous about
casting your pointers and knowing when to do so.  This is a
bummer for the beginners but we all had to go through it unless
we develop on a VAX :-).
-- 
Life : Stephen J. Friedl @ V-Systems, Inc./Santa Ana, CA   *Hi Mom*
CSNet: friedl%vsi.uucp@kent.edu  ARPA: friedl%vsi.uucp@uunet.uu.net
uucp : {kentvax, uunet, attmail, ihnp4!amdcad!uport}!vsi!friedl

throopw@xyzzy.UUCP (Wayne A. Throop) (03/14/88)

> pablo@polygen.uucp (Pablo Halpern)
>> by chris@mimsy.UUCP (Chris Torek)
>> [... on some existing architectures, pointers "naturally" have many
>>      sizes or formats... ]
> However, if I were writing a C compiler, I would choose a size for all
> pointers equal to the size of the largest possible pointer.  This would
> allow code that passed uncasted NULL to work correctly, provided NULL
> is a type as large as a pointer.

This also assumes that the nil pointer has the same format for all
pointer types, and that passing protocols are also the same.  Which,
granted, might all be legislated so by the compiler implementor.  *BUT*
on some architectures, this would impose unacceptable performance
or space consumption tradeoffs.  For example, all pointers might have
to be made twice as large as they might be.  Or access times to unpack
peculiar pointer formats from the "standard" one mandated by the
compiler might be prohibitive.  For example, look at the 80x86 family of
architectures.  Making all pointers 32 bits long and mandating everybody
using the "huge" memory model (or whatever that model is called nowdays)
suffers unacceptable performance degradataion for programs that could
fit into a 16-bit address space.

> This is not because dpANS says it
> should be so, but because so much code would break if it were not.
> Perhaps ANSI should add the restriction that all pointer types must be
> the same size in an effort to "codify common existing practice."

Well.... I don't admit that it is, indeed, "common existing practice" to
assume that pointers are all the same size and the same format, no more
than it is "existing practice" that pointers will fit in ints.  Granted,
there is code that makes this assumption, but it is not so universal as
you assume, since all of these pointer assumptions have, in fact, been
false since nearly the very beginning of C.  I think that dpANS makes
the proper tradeoff, and insists that there be a prototype in scope, or
that an explicit cast occur.

--
How often have I said to you that when you have eliminated the impossible,
whatever remains, however improbable, must be the truth.
    --- Sir Arthur Conan Doyle {The Sign of the Four}
-- 
Wayne Throop      <the-known-world>!mcnc!rti!xyzzy!throopw

gregg@a.cs.okstate.edu (Gregg Wonderly) (03/15/88)

From article <10576@mimsy.UUCP>, by chris@mimsy.UUCP (Chris Torek):
> (You may wish to save this, keeping it handy to show to anyone who
> claims `#define NULL 0 is wrong, it should be #define NULL <xyzzy>'.
> I intend to do so, at any rate.)
> 
> 	[Great example of PR1ME casting problem.]
> 

Another great example is the not so loved Intel segmentation.  For most
Xenix and other pseudo UNIX's for these pseudo computers the following
is true.

When you use small model, everything works great because sizeof(int) == sizeof
((any) *).  If you move to middle model, then everything still works pretty
well except that now sizeof(int) != sizeof ((*)()), but sizeof(int) == sizeof
((anything but function) *).  Now move to large model, and sizeof (int) !=
sizeof ((any) *).  This can really cause problems with routines which accept
NULL as a parameter, because if you do not cast it to (??? *)NULL, then things
break, quite spectacularly.  Now for small and large model, #define NULL
((char *)0) would work, but not for middle model because sizeof (char *) !=
sizeof ((*)()).

Moral:   As always stated in the past, ``Use typecasts, they make your program
         portable, not ugly!''

scott@stl.stc.co.uk (Mike Scott) (03/15/88)

In article <10576@mimsy.UUCP> chris@mimsy.UUCP (Chris Torek) writes:
....
>of type (char *), are 48 bits wide.  All other pointers, such as
>(int *) and (double *), are 32 bits wide.
>
>Now suppose we have the following C code:
>
> 	main()
>	{
> 		f1(NULL);	/* wrong */
> 		f2(NULL);	/* wrong */
> 		exit(0);
> 	}
> 
> 	f1(cp) char *cp; { if (cp != NULL) *cp = 'a'; }
> 	f2(dp) double *dp; { if (dp != NULL) *dp = 2.2; }
>
>There are two lines marked `wrong'.  Now suppose we were to define NULL
>as 0.  Clearly both calls are then wrong: both pass `(int)0', when the
>first should be a 48 bit (char *) nil pointer and the second a 32 bit
>(double *) nil pointer.
>

He suggests the form:
>       main()
>       {
>		f1((char *)NULL);
>		f2((double *)NULL);
>		exit(0);
>	}
>
>The preprocessor changes both NULLs to 0s, and the code remains
>correct.
>

If I could add my 2 pennyworth.  From K&R p192, we read " it is
guaranteed that assignment of the constant 0 to a pointer will
produce a null pointer distinguishable from a pointer to any object".

From page 71: "Within a function, each argument is in effect a local
variable initialized to the value with which the function was called."

It follows at once from these that the integer 0 may be supplied in
any function call where a pointer is expected, and the compiler must
make sure that the proper translation to the correct sort of
null pointer is performed.

[I suppose one might argue about differences between 'assignment' and
'initialisation'. I think it's clear that initialisation of a pointer
to NULL (whatever NULL may be defined as) and assignment of NULL to
that same pointer have to give the same result!]

The real problem is that functions don't have to be declared until
used. Given an ANSII-type declaration, the compiler can sort out
the mess. Until then, I suppose the only way to get portable code is
indeed
to use expicit casts as in the "correct" code above. But until now, I
had no idea that machines with different pointer lengths existed [I'm
rather blinkered by my PDP8/PDP11/VAX spectacles]. I don't really see
how one can guess the needs of various 'strange' machine architectures
that may exists :-(

(Usual disclaimers apply.)
-- 
Regards. Mike Scott (scott@stl.stc.co.uk <or> ...uunet!mcvax!ukc!stl!scott)
phone +44-279-29531 xtn 3133.

janc@palam.eecs.umich.edu (Jan Wolter) (03/16/88)

In article <124@polygen.UUCP> pablo@polygen.uucp (Pablo Halpern) writes:
>From article <10576@mimsy.UUCP>, by chris@mimsy.UUCP (Chris Torek):
>> 
>> Let us begin by postulating the existence of a machine and a compiler
>> for that machine.  This machine, which I will call a `Prime', or
>> sometimes `PR1ME', for obscure reasons such as the fact that it
>> exists, has two kinds of pointers.  `Character pointers', or objects
>> of type (char *), are 48 bits wide.  All other pointers, such as
>> (int *) and (double *), are 32 bits wide.
>
>     ... if I were writing a C compiler, I would choose a size for all
>pointers equal to the size of the largest possible pointer.  This would
>allow code that passed uncasted NULL to work correctly, provided NULL
>is a type as large as a pointer....

For efficiency reasons you probably don't want to get rid of the other
pointer types.  However, why not handle pointers in function calls something
like the way char's are handled in function calls?  When I pass a char to a
function, it is automatically cast to int before being passed.  Why not
automatically cast all pointer types to (char *) before passing them?  I'm
fairly sure C does guarantee that casting a pointer to a pointer to a smaller
object and back always gives you back the same pointer.  This adds slightly
to the overhead in function calls on the Prime, but the simplification of the
interface on just about every other living machine is probably worth it.
This, of course, would only be done if no function prototype is given.

One other vaguely related question:  Which of the following produce a null
pointer?

	int zero = 0;
	char *p1 = 0;             /* this is a null pointer! */
	char *p2 = zero;          /* is this a null pointer? */
	char *p3 = (char *)zero;  /* what's this? */

As I read K&R, a null pointer is only produced when a *constant* 0 is assigned
to a pointer.  When an integer is assigned to a zero, K&R seems to suggest that
a bitwise copy is done, which may not be the same thing at all.  This seems
to be the only case in C where "a=(b=c)" is not equivalent to "a=c,b=c".

While K&R says assignment is a bitwise copy, they say explicitly typecasting
an integer to a pointer gives a machine dependent result.  Thus it seems
possible that the p1, p2, and p3 could be three different pointers.  (Frankly,
the more I read on this subject, the more I think K&R didn't have their minds
entirely clear on this business either.)

				- Jan Wolter
				  janc@crim.eecs.umich.edu

dsill@NSWC-OAS.arpa (Dave Sill) (03/17/88)

In article <800@zippy.eecs.umich.edu> Jan Wolter <janc@palam.eecs.umich.EDU> writes:
>One other vaguely related question:  Which of the following produce a null
>pointer?
>
>	int zero = 0;
>	char *p1 = 0;             /* this is a null pointer! */

Yes.

>	char *p2 = zero;          /* is this a null pointer? */

Maybe.  K&R say assignments between pointers and ints are nonportable,
as are assignments between different types of pointers.

>	char *p3 = (char *)zero;  /* what's this? */

Exactly the same as p2.  K&R define a cast as performing the
conversions required to assign the operand to a variable of the type
of the cast.

>As I read K&R, a null pointer is only produced when a *constant* 0 is assigned
>to a pointer.

Almost, once generated a null pointer can be produced by assignment.
I.e.,
	char *pc1, *pc2;
	pc1 = 0;
	pc2 = pc1;

>While K&R says assignment is a bitwise copy, they say explicitly typecasting
>an integer to a pointer gives a machine dependent result.  Thus it seems
>possible that the p1, p2, and p3 could be three different pointers.

No, at most two different pointers.  p1 is null.  p2 and p3 will be
the same nonportable pointer.

>(Frankly,
>the more I read on this subject, the more I think K&R didn't have their minds
>entirely clear on this business either.)

Maybe you should read some more.

=========
The opinions expressed above are mine.

"Give me a pointer and I'll dereference the world."
					-- David Keppel

dsill@NSWC-OAS.arpa (Dave Sill) (03/17/88)

I wrote:
>In article <800@zippy.eecs.umich.edu> Jan Wolter <janc@palam.eecs.umich.EDU> writes:
>>	char *p2 = zero;          /* is this a null pointer? */
>
>Maybe.  K&R say assignments between pointers and ints are nonportable,
>as are assignments between different types of pointers.
>
>>	char *p3 = (char *)zero;  /* what's this? */
>
>Exactly the same as p2.  K&R define a cast as performing the
>conversions required to assign the operand to a variable of the type
>of the cast.

K&R page 42:
"...	(type-name) expression  ...
The precise meaning of a cast is in fact as if *expression* were
assigned to a variable of the specified type..."

This leads one to the conclusion that
	char *p = zero;
and
	char *p = (char *)zero;
give the same result.  Why, then, does the former cause a warning
about an illegal combination of pointer and integer?  Is the sole
function of the cast in the latter to prevent such a warning?

>>(Frankly,
>>the more I read on this subject, the more I think K&R didn't have their minds
>>entirely clear on this business either.)
>
>Maybe you should read some more.

I'd suggest reading the dpANS-C, where this is all much more
well-defined.

=========
The opinions expressed above are mine.

"Meanings receive their dignity from words instead of giving it to them."
					-- Blaise Pascal

franka@mmintl.UUCP (Frank Adams) (03/17/88)

In article <800@zippy.eecs.umich.edu> janc@palam.eecs.umich.edu (Jan Wolter) writes:
>As I read K&R, a null pointer is only produced when a *constant* 0 is assigned
>to a pointer.  When an integer is assigned to a zero, K&R seems to suggest
>that a bitwise copy is done, which may not be the same thing at all.  This
>seems to be the only case in C where "a=(b=c)" is not equivalent to "a=c,b=c".

It isn't.  Try:

double d;
int i;
d = (i = 1.5);

In general, "a=(b=c)" is eqivalent to "b=c,a=b".
-- 

Frank Adams                           ihnp4!philabs!pwa-b!mmintl!franka
Ashton-Tate          52 Oakland Ave North         E. Hartford, CT 06108

g-rh@cca.CCA.COM (Richard Harter) (03/18/88)

In article <636@acer.stl.stc.co.uk> scott@acer.UUCP (Mike Scott) writes:

>If I could add my 2 pennyworth.  From K&R p192, we read " it is
>guaranteed that assignment of the constant 0 to a pointer will
>produce a null pointer distinguishable from a pointer to any object".
>
>From page 71: "Within a function, each argument is in effect a local
>variable initialized to the value with which the function was called."
>
>It follows at once from these that the integer 0 may be supplied in
>any function call where a pointer is expected, and the compiler must
>make sure that the proper translation to the correct sort of
>null pointer is performed.
>
>[I suppose one might argue about differences between 'assignment' and
>'initialisation'. I think it's clear that initialisation of a pointer
>to NULL (whatever NULL may be defined as) and assignment of NULL to
>that same pointer have to give the same result!]
>
>The real problem is that functions don't have to be declared until
>used. Given an ANSII-type declaration, the compiler can sort out
>the mess. Until then, I suppose the only way to get portable code is
>indeed to use expicit casts as in the "correct" code above.

Actually, the NULL=0 question is part of a more general problem that
may not be addressed correctly in ANSI C prototypes.

In a single routine there is no problem with NULL=0 because that usage
is guaranteed by the language.  The problem arises when we call one
routine from another and pass arguments.  For everything to work right
the calling routine must pass the right number of arguments to the
called routine, with all arguments having the right type.  The problem
is that the compiler has no way of knowing what these are -- it takes
it on faith from the code present in the routine that has the call.
In the case of an uncasted 0, it sees an integer, and sets up the
call as though it were passing an integer.  This fails unless, by chance,
the architecture is such that a NULL pointer and an integer 0 happen
to look alike.

However the problem is more general, and I am not sure that ANSI
function prototypes handle it properly.  [I don't have the spec,
so I am winging it a bit here.]  The problem breaks into three parts.
Part 1 is to provides a means for both the calling routine and the
called routine to have a description of the calling sequence.  Part
2 is to ensure that they have the *same* description.  Part 3 is to
ensure synchronization, i.e. to ensure that the routines affected
change when the description changes.

Function prototypes addresses the first question.  The second question
is the tricky one.  The natural way to do this is to put the decsription
in one place, which is shared by both caller and called routines.  In
the UNIX/C environment this means putting them in a header file.  It
also has implications for the called routine.  Does the called routine
still have an inline calling sequence declaration?  Look at this:

#include "prototypes.h"
....
type_of_foo foo(a,b)
  type_of_a a;
  type_of_b b;
{....}

I don't know if this is what ANSI C expects, but it's not too hot if
it is, because we now have two descriptions of the calling sequence.
The right thing is simply

#include "prototypes.h"
....
type_of_foo foo(a,b) {....}

In fact, even type_of_foo should be omitted, since that is really part
of the description.

I have the (perhaps mistaken) impression that ANSI function prototypes
cannot be set up this way in the called routine.  If they can't the next
best thing is for the compiler to check whether the function prototype
description in "prototypes.h" matches the actual declaration.

This may be even better from the standpoint of visibility, since the
contents of the header file containing the description are not visible
when you are looking at code referencing the the description.

Another issue is that the called routine may not even have a copy of
the description -- in that case the value of the description file is
much less, since the routine can change without the description file
changing and vice versa.

Part 3 (synchronization) can be handled using make with the right
dependencies, if the other problems are met.

These are some of the issues, as I see them.  I'm not sure that  the
ANSI C function prototypes deal with them all 'properly', but I
haven't seen anything on the net discussing the impact of prototypes
on the called routines.
-- 

In the fields of Hell where the grass grows high
Are the graves of dreams allowed to die.
	Richard Harter, SMDS  Inc.

ok@quintus.UUCP (Richard A. O'Keefe) (03/18/88)

In article <25652@cca.CCA.COM>, g-rh@cca.CCA.COM (Richard Harter) writes:
> I don't know if this is what ANSI C expects, but it's not too hot if
> it is, because we now have two descriptions of the calling sequence.
> The right thing is simply
> 
> #include "prototypes.h"
> ....
> type_of_foo foo(a,b) {....}
> 
> In fact, even type_of_foo should be omitted, since that is really part
> of the description.
> 
It depends what you mean by "the right thing".
If you mean "whatever means I have less to write", fine.
If you mean "whatever makes C more like Pascal", fine.
If you mean "whatever will make the code easier to maintain",
you're dead wrong.

When as Joe Maintainer I come along and try to figure out what your function
foo() does, I want to be able to see what the arguments and result are,
which means that I want them *right* *there* with the rest of foo(), where
I can see them.  In fact, I want all the immediately useful information to
be there where I don't have to hunt all over file space to find it.
(Note that I said *immediately* useful.  If there are 10 pages of text
describing the thing, and a couple of hundred test cases, give me in the
source code the names of the files that contain them.)

This was one of the biggest blunders in Pascal:  if you declare a procedure
'forward' in Pascal, you don't even get the NAMES of the parameters at the
actual definition.  Which meant that competent Pascal programmers always
put a copy of the parameter list there as a comment.

This is one of the things the ANSI C committee got right.
There are a lot of such things.

gwyn@brl-smoke.ARPA (Doug Gwyn ) (03/20/88)

In article <25652@cca.CCA.COM> g-rh@CCA.CCA.COM.UUCP (Richard Harter) writes:
>I don't know if this is what ANSI C expects, but it's not too hot if
>it is, because we now have two descriptions of the calling sequence.

Yes, but since a diagnostic is required for such a declaration mismatch,
you get the opportunity to fix it before it's actually used.

lai@vedge.UUCP (David Lai) (03/29/88)

In article <2448@umd5.umd.edu>, louie@trantor.umd.edu (Louis A. Mamakos) writes:
> 
> On, for example, our Unisys 1100 machine, a "regular"
> pointer is 8 bytes long (2 words, 72 bits).  A pointer to a function is 
> 64 bytes long (8 words, 288 bits).  
The math is wrong, 64 bytes ( 16 words, 576 bits).

Our news feeds are very late so this reply may have been posted already
-- 
The views expressed are those of the author, and not of Visual Edge or Usenet
David Lai (vedge!lai@oliver.cs.mcgill.edu || ...decvax!musocs!vedge!lai)

ray@micomvax.UUCP (Ray Dunn) (03/30/88)

In article <10576@mimsy.UUCP> chris@mimsy.UUCP (Chris Torek) writes:
>
> [the definitive, correct explanation of NULL and pointers]
>

In article <636@acer.stl.stc.co.uk> scott@acer.UUCP (Mike Scott) totally
f*cks things up again by writing:
>
>If I could add my 2 pennyworth.  From K&R p192, we read " it is
>guaranteed that assignment of the constant 0 to a pointer will
                 ****************************
>produce a null pointer distinguishable from a pointer to any object".
>
>From page 71: "Within a function, each argument is in effect a local
>variable initialized to the value with which the function was called."
>
>It follows at once from these that the integer 0 may be supplied in
                                    *****************************
>any function call ......


Mike, is your slip showing clearly enough??

The compiler treats the *constant* zero specially, as being any required
number of bits to match the type of the pointer lvalue *in an assignment* -
i.e.  that syntactic thingamy which uses the "=" symbol!!!!

In other words, when ='ed to a pointer, the constant 0 is automatically
cast to the correct type.

In all other cases, 0 is just like any other int, constant or variable, and
must be cast to the correct type, as it occupies the number of bits defined
for ints in that implementation.

Everybody - *LISTEN* - Chris is *RIGHT* - don't write any other explanation
- read Chris' article again, and *LEARN*.

Ray Dunn.  ..{philabs,mnetor,musocs}!micomvax!ray

davidsen@steinmetz.steinmetz.ge.com (William E. Davidsen Jr) (04/04/88)

There has been a great deal of misunderstanding of the use of zero and
pointers. It seems clear in K&R and practice that assignment of a zero
to a pointer produces a NULL pointer of the appropriate type. What is
incorrectly assumed is that zero *is* a NULL pointer.

People (usually ;>) have no problem with the idea the while assigning a
zero to a float gives it the vaule 0.0, in most implementations the
float value does not have all bits set to zero.

There are two places in which a zero will not work as expected:
  In expressions, adding an int expression to a pointer produces a
pointer result, while adding an int expression to an int constant (zero)
produces an int result. Since there does not seem to be a good use for
treating the sum of two ints an address in any remotely portable
program, this is of interest but not concern.

  In procedure calls, however, on many machines the arguments must have
the correct type for portability because either the size of int and
pointer are not the same, or because the size and content of various
pointer types are not the same.

  If prototypes are used, then coding need not be as precise, since use
of 0 where 0L is needed, or zero where (char *)0 is needed will be
corrected by the compiler. Without prototypes the arguments will need to
be correct if the program is to be portable.

There are many programs which "have worked for years" which are not
portable, because of this lack of typing on arguments. Most of these run
on any machine which has the size of int equal sizeof pointer, and all
pointers are the same in content. This includes the VAX and 68000
family. Other machines, such as some Data General models, Cray, small
Intel processors, SPARC, and some non-UNIX C compilers on any machine
will not accept this lack of explicit typing.
-- 
	bill davidsen		(wedu@ge-crd.arpa)
  {uunet | philabs | seismo}!steinmetz!crdos1!davidsen
"Stupidity, like virtue, is its own reward" -me

guy@gorodish.Sun.COM (Guy Harris) (04/05/88)

> Most of these run on any machine which has the size of int equal sizeof
> pointer, and all pointers are the same in content. This includes the VAX
> and 68000 family. Other machines, such as some Data General models, Cray,
> small Intel processors, SPARC, and some non-UNIX C compilers on any machine
> will not accept this lack of explicit typing.

Umm, I agree with your sentiments whole-heartedly, but I do have to point out
that on SPARC, sizeof (mumble *) == sizeof (frotz *) == sizeof int for all
values of "mumble" and "frotz", and that a "mumble *" contains the address of
the appropriate byte of the "mumble" in question (SPARC being big-endian) for
all values of "mumble".  For better or worse, on SPARC you can get away with
passing 0 or NULL to routines expecting a pointer.  (You'd better not try to
*dereference* that null pointer, though.)

Using tricks such as

	printf(fmt, args)
		char *fmt;
		char *args;
	{
		char *ap;

		...

		ap = &args;

		while ((c = *fmt++) != '\0') {

			switch (c) {

			case 'd':
				int_value = *((int *) ap);
				ap += sizeof int;

		...
	}

won't work on SPARC - you have to use the "varargs" stuff - but that's a
different matter, as is the fact that SPARC requires (2,4,8)-byte alignment of
(2,4,8)-byte atoms (many other chips require some or all of these alignments as
well, including some CISCs such as the WE32100).

ark@alice.UUCP (04/05/88)

In article <10229@steinmetz.steinmetz.ge.com>, davidsen@steinmetz.UUCP writes:

> People (usually ;>) have no problem with the idea the while assigning a
> zero to a float gives it the vaule 0.0, in most implementations the
> float value does not have all bits set to zero.

I am overwhelmed by curiosity.  Can you give me examples of
three machine architectures in which the floating-point value
0 does not have all its bits set to zero?

djones@megatest.UUCP (Dave Jones) (04/05/88)

in article <10229@steinmetz.steinmetz.ge.com>, davidsen@steinmetz.steinmetz.ge.com (William E. Davidsen Jr) says:
> 
> There has been a great deal of misunderstanding of the use of zero and
> pointers. It seems clear in K&R and practice that assignment of a zero
> to a pointer produces a NULL pointer of the appropriate type. What is
> incorrectly assumed is that zero *is* a NULL pointer.
> 

... [ Lot's of good, true, stuff about pointers etc. ]

> There are many programs which "have worked for years" which are not
  ^^^^^ ^^^ ^^^^ ^^^^^^^^ ^^^^^  ^^^^ ^^^^^^ ^^^ ^^^^^  ^^^^^ ^^^ ^^^
> portable, because of this lack of typing on arguments. Most of these run
  ^^^^^^^^
> on any machine which has the size of int equal sizeof pointer, and all
> pointers are the same in content. This includes the VAX and 68000
> family. Other machines, such as some Data General models, Cray, small
> Intel processors, SPARC, and some non-UNIX C compilers on any machine
		    ^^^^^
> will not accept this lack of explicit typing.
> -- 
> 	bill davidsen		(wedu@ge-crd.arpa)
>   {uunet | philabs | seismo}!steinmetz!crdos1!davidsen
> "Stupidity, like virtue, is its own reward" -me


SPARC?  Really?  REALLY??   What have they done?  Please clarify.

This is news to me.  I was naively assuming that since the current
class of Sun workstations is MC68020, they would attempt to be sure
that 68000 C programs would be portable to SPARC. (Even non-portable ones
which "have worked for years.")

I'm stunned.  Really.  REALLY!!  Stunned.

Please tell me this was just an April Fool's joke that got here a
little late.


		Dave (int*) Jones

edw@IUS1.CS.CMU.EDU (Eddie Wyatt) (04/06/88)

Aside:

> People (usually ;>) have no problem with the idea the while assigning a
> zero to a float gives it the vaule 0.0, in most implementations the
> float value does not have all bits set to zero.

  I do believe IEEE and Vax's version of float is a bit pattern
consisting of all zeros.  So what are these implementations that don't
use zero bit pattern?


-- 

Eddie Wyatt 				e-mail: edw@ius1.cs.cmu.edu

rbutterworth@watmath.waterloo.edu (Ray Butterworth) (04/06/88)

About the best way I've seen of convincing people of why you
can't simply use 0, or NULL, or 0L, or (void*)0, or any such
single token as a null pointer is to consider the following
function call:

auto char *bigstring;
bigstring = concatenate("This", " ", "is", " ", "it", ".", (char*)0);

where concatenate() mallocs enough memory, copies all its arguments
into one long string, and returns a pointer to that string.

It takes a variable number of arguments of which the last must be
a null (char *) pointer.

Now on any compiler for which a null character pointer is different
from the int 0, whether with a different size or a non-zero bit pattern,
there is absolutely no way that the compiler can automatically generate
a correct value for the last argument.  Even prototypes won't help
in this case.

You may use (char*)0, NULL_P(char*), (char*)NULL, or whatever
your favourite is.  But you must explicitly put the (char*) type
in there somehow or other.

If you simply use 0, NULL, 0L, or something that doesn't mention
(char*), it might just happen to work on YOUR compiler NOW, but
there is no reason you should assume that this code will work
anywhere else at any other time.  It is simply wrong.

And before someone suggests defining NULL as (char*)0, remember
that there are machines for which sizeof(char*) is not the same
as sizeof(long*).  All that does is give you something else that
is wrong but happens to work in a few more circumstances.  It is
still wrong.

davidsen@steinmetz.steinmetz.ge.com (William E. Davidsen Jr) (04/06/88)

  Although the size of int and pointers may be the same on SPARC, my
impression from limited testing is that the machine can't access
unalligned data, and that the compiler doesn't catch this. Thus if I do
something dumb like:

	char m[5];
	a = foo(&m[0], &m[1]);
where:
	foo(x,y)
	  int *x, *y;

that at least one of the pointers will not be on a 4 byte boundary, and
therefore will not work as expected by people who use a VAX, 68000,
80x86, etc. This was the reason for my caution about SPARC.

  If this is not the case, please correct me, as my access to a Sun4 was
limited, and other things might have caused the problem.
-- 
	bill davidsen		(wedu@ge-crd.arpa)
  {uunet | philabs | seismo}!steinmetz!crdos1!davidsen
"Stupidity, like virtue, is its own reward" -me

davidsen@steinmetz.steinmetz.ge.com (William E. Davidsen Jr) (04/06/88)

In article <1323@PT.CS.CMU.EDU> edw@IUS1.CS.CMU.EDU (Eddie Wyatt) writes:

|   I do believe IEEE and Vax's version of float is a bit pattern
| consisting of all zeros.  So what are these implementations that don't
| use zero bit pattern?

  Honeywell 600 and DPS series for sure (0.0 = 0400000000), I believe
some IBM minis, and some Data General. Like "pointer same as int," it
works on many machines but assumes a hardware dependency which may be
avoided by careful typing.
-- 
	bill davidsen		(wedu@ge-crd.arpa)
  {uunet | philabs | seismo}!steinmetz!crdos1!davidsen
"Stupidity, like virtue, is its own reward" -me

davidsen@steinmetz.steinmetz.ge.com (William E. Davidsen Jr) (04/06/88)

In article <432@goofy.megatest.UUCP> djones@megatest.UUCP (Dave Jones) writes:

| SPARC?  Really?  REALLY??   What have they done?  Please clarify.

  My understanding of SPARC, having read documentation and tried some
programs, is that SPARC requires that int, float, and long be alligned
rather than on an arbitrary boundary. This means that just typing a
pointer to char or short as pointer to {int long float double} will not
produce code which loads consecutive bytes starting at the 1st character
address.

  If you might be doing something like building a raw data buffer, by:
	*((int *)bufptr = intval;	/* char *bufptr */
	bufptr += sizeof(int);
it is portable to any machine which doesn't require allignment, but for
true portability something like the following must be done:
	memncpy(bufptr, &intval, sizeof int);
	bufptr += sizeof int;

  I am not claiming or even implying that this is going to break any
large number of programs, just that SPARC has more restrictions on
addressing than the 68000 series.  I don't have dpANS here, there may be
a better procedure to use than memncpy. 

  It also means that structures are not packed the same way in some
cases, I assume, but was not able to verify.
-- 
	bill davidsen		(wedu@ge-crd.arpa)
  {uunet | philabs | seismo}!steinmetz!crdos1!davidsen
"Stupidity, like virtue, is its own reward" -me

davidsen@steinmetz.steinmetz.ge.com (William E. Davidsen Jr) (04/06/88)

In article <7792@alice.UUCP> ark@alice.UUCP writes:
}In article <10229@steinmetz.steinmetz.ge.com>, davidsen@steinmetz.UUCP writes:
}
}> People (usually ;>) have no problem with the idea the while assigning a
}> zero to a float gives it the vaule 0.0, in most implementations the
					      ^^^^
	Foot in mouth time... I think "some" would be more correct.
}> float value does not have all bits set to zero.
}
}I am overwhelmed by curiosity.  Can you give me examples of
}three machine architectures in which the floating-point value
}0 does not have all its bits set to zero?

No. I can tell you two series of systems which have non-zero 0.0
(Honeywell 6000 and DPS), two which I'm told have non-zero 0.0 (mid 70's
IBM minis and Data General). My reasoning is as follows:

  given:	there are machines which do have C and don't have
		flat 0 expressed as all bits zero,

  and		the proposed C standard doesn't say that any machine
		running C must have float 0 be all bits zero,

  and		I have no reason to think that innovation and
		standards have stopped evolving,

  therefore:	I conclude that assuming float 0 to be all bits
		zero, or any other machine independent integer value,
		is non-portable, now and in the future.

  In light of the previous discussion, I was discussing "really
portable" as opposed to "portable to many machines."
-- 
	bill davidsen		(wedu@ge-crd.arpa)
  {uunet | philabs | seismo}!steinmetz!crdos1!davidsen
"Stupidity, like virtue, is its own reward" -me

sjs@spectral.ctt.bellcore.com (Stan Switzer) (04/07/88)

In reference to:
> > People (usually ;>) have no problem with the idea the while assigning a
> > zero to a float gives it the vaule 0.0, in most implementations the
> > float value does not have all bits set to zero.

And:
>   I do believe IEEE and Vax's version of float is a bit pattern
> consisting of all zeros.  So what are these implementations that don't
> use zero bit pattern?

The Honeywell 6000 Series and GE 625/635 machines represented NORMALIZED
float zero as o400000000000 (36 bits, msb ON).  This may date back to its
grand-uncle the 709.

BTW, all 0 bits WAS a float zero, but it wasn't normalized.  The results of
most float operations on unnormalized values was undefined.  I suspect that
additions and subtractions would lose precision in the process
of alligning the points.

hook@jvnca.csc.org (Ed Hook) (04/07/88)

In article <1323@PT.CS.CMU.EDU> edw@IUS1.CS.CMU.EDU (Eddie Wyatt) writes:
>Aside:
>
>> People (usually ;>) have no problem with the idea the while assigning a
>> zero to a float gives it the vaule 0.0, in most implementations the
>> float value does not have all bits set to zero.
>
>  I do believe IEEE and Vax's version of float is a bit pattern
>consisting of all zeros.  So what are these implementations that don't
>use zero bit pattern?
>

   There's at least one architecture with this characteristic. The Control
Data Cyber 205 ( and its successor, the ETA^10 ) employ a 64-bit floating
point representation in which zero appears as

                    8000 0000 0000 0000 ;

in fact, any word whose most significant nybble is an '8' is = 0.0 in this
system - the example above is the flavor of 0.0 which is produced by any
computation  whose result is zero ( and, thus, is the most familiar example 
of 0.0 ).

   I can't seem to find my copy of the IEEE Floating Point Standard & so can't
cite chapter & verse, BUT I don't believe that it requires a valid floating
point 0.0 to be represented by a bit pattern having all bits = 0. I say this
because it didn't appear to me that the 205's representation of floating point
quantities was in violation of the standard on this score, when I went through
the document recently. If I'm wrong in holding this opinion, I would appreciate
being set straight ...

Ed Hook
Control Data Corporation
John von Neumann National Supercomputer Center
Princeton, NJ

"If I said it, it's MY opinion ... at least for now ..."

pardo@june.cs.washington.edu (David Keppel) (04/09/88)

In article <6594@bellcore.bellcore.com> sjs@spectral.UUCP (Stan Switzer) writes:
>In reference to:
>> > People (usually ;>) have no problem with the idea the while assigning a
>> > zero to a float gives it the vaule 0.0, in most implementations the
>> > float value does not have all bits set to zero.
>
>And:
>>   I do believe IEEE and Vax's version of float is a bit pattern
>> consisting of all zeros.  So what are these implementations that don't
>> use zero bit pattern?

[ some machines that use non-0's for 0.0 ]

According to my hardware book [DEC85] the F_floating and D_floating form on
the VAX both represent 0.0 by having the sign and magnitude bits <7:15> zero,
with any mantissa.  Similarly the G_floating type is 0.0 when <4:15> are zero
bits and the H_floating type 0.0 when <0:15> are zero bits.

Thus the following are perfectly valid F_floating representations for 0.0:

    00 00 00 00
    ff ff 00 7f
    43 21 00 12

And there are analogs for the other types.  This does mean (for the VAX)
that assigning at least 16 bits of zeroes (for F_floating and D_floating)
or 32 bits of zeroes (for G_floating and H_floating) give 0.0, which I think
was the spirit of the original poster.  I would like to point out, however,
that it is very much *not* possible to compare bit patterns of a float to
*anything* in 8- 16- or 32- bit quantities and determine whether the value
is 0.0 *except* for the H_floating type (which is 16 bytes long).

[DEC85] VAX ARCHITECTURE REFERENCE MANUAL (c) 1985 Digital Equipment
Corporation, Maynard Massachusetts.  Copied without permission.

	;-D on  ( VAX?  What's that? )  Pardo

news@ism780c.UUCP (News system) (04/09/88)

In article <6594@bellcore.bellcore.com> sjs@spectral.UUCP (Stan Switzer) writes:
>In reference to:
>The Honeywell 6000 Series and GE 625/635 machines represented NORMALIZED
>float zero as o400000000000 (36 bits, msb ON).  This may date back to its
>grand-uncle the 709.
>
>BTW, all 0 bits WAS a float zero, but it wasn't normalized.  The results of
>most float operations on unnormalized values was undefined.  I suspect that
>additions and subtractions would lose precision in the process
>of alligning the points.

Are you sure?  on the 709 all zero bits is the NORMALIZED zero.  quoting from
the 709 reference manual on page 8 (I still have one) "A normal zero has no
bits in both the charactersic and the fraction."  The form o400000000000  is
the unnormalized form and using this one is the way to get "funny" results.
My recloction is that the GE 635 (I no longer have that manual) did indeed
copy the 709.

     Marv Rubinstein -- Computer historian