[net.lang.c] offsets in structures.

john@orion.UUCP (10/05/84)

[]
Here's a little *trick* that I picked up today to calculate offsets
of elements in structures.

#define OFFSET(X, Y)  ((struct X *)0)->Y


e.g.
	offset for an integer field  --  &OFFSET(mystruct, anyint)
	offset for a character array --  OFFSET(mystruct, arrayname)

Although there are more readable ways to write this using +/- and & I reckon
its kinda neat.

Also from the same source....
e.g. to rename files with suffix .n without suffix

	for i in *.n
	do
		ifs=$IFS; IFS=.; set $i; IFS=$ifs
		cp $i $1
	done

orion!john

guido@mcvax.UUCP (Guido van Rossum) (10/10/84)

Watch out!  I did a similar thing to calculate the width of a structure:

	(int) ((struct foo *)0 + 1)

This would give me the sizeof a struct foo, ROUNDED UP to the necessary
alignment.  It worked fine, until one day, I ported my program to an
IBM PC with a Lattice C compiler.  There it would always evaluate to 1
(apparently the compiler cancelled the two casts against each other).

--
	Guido van Rossum, "Stamp Out BASIC" Committee, CWI, Amsterdam
	guido@mcvax.UUCP

"Immorality may be something, but it does not take the place of virtue
and three square meals a day."

ron@brl-tgr.ARPA (Ron Natalie <ron>) (10/10/84)

This is useful except on the VAX it don't work for bit fields (now
before you scream, let me demonstrate...)

	sturct	foo  {
		int	x;
		char	a:4, b:4;
		char	c;
	};

You can't find the offset of the char that contains a and b.
Just another reason for not using bitfields, I guess.

-Ron

jdb@mordor.UUCP (John Bruner) (10/11/84)

A problem with expressions like

	&((foo *)0)->bar 

(which I confess I have used myself) is that (foo *)0 is NOT a pointer
whose value is zero, it is a special case -- the NULL pointer.  The
two are identical only if your machine represents NULL as an integer
zero.  If your machine has a special representation for NULL/nil
pointers (e.g.  if it is a tagged architecture with a special NULL/nil
pointer tag) then (foo *)0 and (int)0 are distinct.  The above
expression is based upon the use of (foo *)0 as a pointer to zero,
not as the NULL pointer.

I believe that the above expression should be an illegal attempt to
perform address calculation using NULL.  Consider the following
"equivalent" code sequence on a machine for which NULL is not
represented as an integer zero; the address calculation here will blow
up, since "x" is assigned the NULL pointer, not a pointer to zero:

	foo *x;

	x = (foo *)0;
	... = &x->bar;
-- 
  John Bruner (S-1 Project, Lawrence Livermore National Laboratory)
  MILNET: jdb@mordor.ARPA [jdb@s1-c]	(415) 422-0758
  UUCP: ...!ucbvax!dual!mordor!jdb 	...!decvax!decwrl!mordor!jdb

guy@rlgvax.UUCP (Guy Harris) (10/12/84)

> Watch out!  I did a similar thing to calculate the width of a structure:
> 
> 	(int) ((struct foo *)0 + 1)
> 
> This would give me the sizeof a struct foo, ROUNDED UP to the necessary
> alignment.  It worked fine, until one day, I ported my program to an
> IBM PC with a Lattice C compiler.  There it would always evaluate to 1
> (apparently the compiler cancelled the two casts against each other).

That's a broken compiler.  "(struct foo *)0" is of type "pointer to 'struct
foo'", so adding 1 to it should make it point to the "next" object of type
"struct foo".

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

kpmartin@watmath.UUCP (Kevin Martin) (10/12/84)

>Watch out!  I did a similar thing to calculate the width of a structure:
>
>	(int) ((struct foo *)0 + 1)
>
>This would give me the sizeof a struct foo, ROUNDED UP to the necessary
>alignment.
>	Guido van Rossum
>	guido@mcvax.UUCP


First off, if your 'sizeof' operator doesn't give you the size already rounded
up to a multiple of the required alignment, you're in trouble!

Second, on some hardware, casting a pointer to an integeral type *DOESN'T
GIVE A BYTE INDEX FROM BYTE ZERO*! On a Honeywell Level 66 (or a 6000
or a DPS8 or a DPS88...), the above expression would give the value
01000000 (if sizeof(struct foo) == sizeof(int) which is 4 bytes).

A better expression, which is a bit closer to legit (but still
subject to compiler bugs) is:
    (char *)((struct foo *)0 + 1) - (char *)0
Or, for getting the offset of a struct element,
    (char *)(&((struct foo *)0)->element) - (char *)0

The two reasons that these still aren't legit are:
(1) They should really use NULL, rather than 0 (but this is just being picky)
(2) It is not at all clear that any operation on a NULL pointer other than
    comparison and asignment is allowed. The above code does a pointer+int,
    then a pointer difference, both using NULL pointers.
                     Kevin Martin, UofW Software Development Group

keesan@bbncca.ARPA (Morris Keesan) (10/13/84)

>That's a broken compiler.  "(struct foo *)0" is of type "pointer to 'struct
>foo'", so adding 1 to it should make it point to the "next" object of type
>"struct foo".
>
>	 Guy Harris
>

As John Bruner has already pointed out, this is not at all broken behavior on
the part of the compiler.  Section 7.14 of the C Reference Manual (p. 192 in
K&R) says, " . . . it is guaranteed that assignment of the constant 0 to a
pointer will produce a null pointer distinguishable from a pointer to any
object."  This means that although "(struct foo *)0" is indeed of type
"pointer to 'struct foo'", it is guaranteed not to point to any object, so it
is meaningless to refer to the "next" object.
-- 
			    Morris M. Keesan
			    {decvax,linus,ihnp4,wivax,wjh12,ima}!bbncca!keesan
			    keesan @ BBN-UNIX.ARPA

lambert@mcvax.UUCP (Lambert Meertens) (10/13/84)

>
>That's a broken compiler.  "(struct foo *)0" is of type "pointer to 'struct
>foo'", so adding 1 to it should make it point to the "next" object of type
>"struct foo".

In general, we can hope to find the `array offset' of a struct by

        (int)((struct foo*)N+1) - (int)(struct foo*)N

If N = 0, this can be simplified to (int)((struct foo*)0+1).  But
there is something special about casting 0 to a pointer: the
result does not point, so the notion of `next object pointed to'
is ill defined.  Now what if N != 0?  As far as I can see, it is
impossible to *deduce* from K&R that the result is not simply 1.
Assume, for example, a compiler that ensures that values whose
size in bytes is S are always aligned on a multiple of P(S),
where P(S) is a power of two that is at least S.  This is,
admittedly, silly, but not forbidden.  Now the internal addresses
of S-sized values are all of the form N*P(S), and the code could
then use N as the internal representation of the pointer.  The
code for ++ is then simply: add 1 to the integer representing the
pointer.  For pointer following, the code should multiply the
pointer N by P(S), where S is the size of the type of objects
pointed to (which looks silly but might be cheap on the
hardware).  Casts to int are now supercheap: internally they are
the identity function.  The above expression evaluates then to 1.
    With this scheme, one cannot guarantee that

        (int)(struct foo*)p = (int)(struct bar*)p

but I do not see how this identity would follow from the C
scriptures.

     Lambert Meertens
     ...!{seismo,philabs,decvax}!lambert@mcvax.UUCP
     CWI (Centre for Mathematics and Computer Science), Amsterdam
-- 

     Lambert Meertens
     ...!{seismo,philabs,decvax}!lambert@mcvax.UUCP
     CWI (Centre for Mathematics and Computer Science), Amsterdam

kendall@wjh12.UUCP (Sam Kendall) (10/13/84)

> A better expression, which is a bit closer to legit ...
> ... for getting the offset of a struct element,
>     (char *)(&((struct foo *)0)->element) - (char *)0
> ...
> (2) It is not at all clear that any operation on a NULL pointer other than
>     comparison and asignment is allowed. The above code does a pointer+int,
>     then a pointer difference, both using NULL pointers.
>                      Kevin Martin, UofW Software Development Group

(2) is correct because, it seems to me, if the reference manual defines
no operations on a null pointer besides copying, equality comparison,
and casting, then no other operations make sense (looking only at the
language rather than at implementations of it).

   The expression can be made portable, at least under UNIX, by
having
	extern end;
somewhere in a header file, and then replacing "0" in the expression by
"&end".  For maximum portability, of course, use a real object of type
struct foo in the expression.

	Sam Kendall	  {allegra,ihnp4,ima,amd}!wjh12!kendall
	Delft Consulting Corp.	    decvax!genrad!wjh12!kendall

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

> >That's a broken compiler.  "(struct foo *)0" is of type "pointer to 'struct
> >foo'", so adding 1 to it should make it point to the "next" object of type
> >"struct foo".
> >
> >	 Guy Harris
> >
> 
> As John Bruner has already pointed out, this is not at all broken behavior on
> the part of the compiler.  Section 7.14 of the C Reference Manual (p. 192 in
> K&R) says, " . . . it is guaranteed that assignment of the constant 0 to a
> pointer will produce a null pointer distinguishable from a pointer to any
> object."  This means that although "(struct foo *)0" is indeed of type
> "pointer to 'struct foo'", it is guaranteed not to point to any object, so it
> is meaningless to refer to the "next" object.

Yes, but would you trust any compiler that optimized

	(int) ((struct foo *)0 + 1)

into 1 not to optimize

	(int) ((struct rp11_regs *)177550 + 1)

into 177551?  At best, it may just be optimizing (0 of any kind) + N into N;
it should still check the kind of 0 before it forges ahead with this
optimization.

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

gwyn@brl-tgr.ARPA (Doug Gwyn <gwyn>) (10/14/84)

The propagation of typecasts prematurely into expressions is an old PCC
bug fixed years ago when the "repainting" stuff was added.  It would
sure be nice if people would start with the latest version of software
when they do their adaptations.

gwyn@brl-tgr.ARPA (Doug Gwyn <gwyn>) (10/14/84)

As has been discussed before, a 0 pointer in C is not
appropriately mapped to a special nil pointer in tagged
architectures.

guy@rlgvax.UUCP (Guy Harris) (10/15/84)

> As has been discussed before, a 0 pointer in C is not
> appropriately mapped to a special nil pointer in tagged
> architectures.

Do you mean "(existing) implementations of C on tagged architectures do
not perform the appropriate mapping of a 0 pointer to a special nil
pointer", or "it is not appropriate to map a 0 pointer to a special
nil pointer in a tagged architecture?"  If the latter, I disagree; there
is no committment to the bit pattern of a 0 pointer in C, and no committment
to being able to dereference it.  As such, I see no reason not to map a 0
pointer onto bit pattern you want, as long as 1) it's distinct from *all*
bit patterns for legitimate pointers in C (i.e., your C implementation must
make sure no C function or variable has an address with that bit pattern) and
2) it fits in the same number of bits as any other pointer.

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

chris@umcp-cs.UUCP (Chris Torek) (10/15/84)

It would be interesting to make a version of the VAX compilers that
map NIL pointers to 0x80000000 (or 0xffffffff).  Whee!
-- 
(This mind accidently left blank.)

In-Real-Life: Chris Torek, Univ of MD Comp Sci (301) 454-7690
UUCP:	{seismo,allegra,brl-bmd}!umcp-cs!chris
CSNet:	chris@umcp-cs		ARPA:	chris@maryland

geoff@desint.UUCP (Geoff Kuenning) (10/16/84)

>   The expression can be made portable, at least under UNIX, by
>having
>	extern end;
>somewhere in a header file, and then replacing "0" in the expression by
>"&end".  For maximum portability, of course, use a real object of type
>struct foo in the expression.

>	Sam Kendall

(What Sam is talking about would then read like this:)

>     (char *)(&((struct foo *)&end)->element) - (char *)&end

Good idea, except am not sure &end is the best symbol to use.  Is &end
actually guaranteed to refer to a legal address by itself?  What about the
other "&e" symbols:  etext and edata?  Is it legal is we replace "&end" with
"(&end-sizeof (struct foo))"?
-- 
	Geoff Kuenning
	First Systems Corporation
	...!ihnp4!trwrb!desint!geoff

gwyn@brl-tgr.ARPA (Doug Gwyn <gwyn>) (10/16/84)

I don't want to repeat all the earlier arguments, but consider:
A null pointer can be produced legally by arithmetic operations,
as well as by hard-coded (foo *)0 constants in the source.  To
use a special nil value would require run-time checks to be applied
to much pointer arithmetic.  This is contrary to the intent of C.

guy@rlgvax.UUCP (Guy Harris) (10/17/84)

> I don't want to repeat all the earlier arguments, but consider:
> A null pointer can be produced legally by arithmetic operations,
> as well as by hard-coded (foo *)0 constants in the source.  To
> use a special nil value would require run-time checks to be applied
> to much pointer arithmetic.  This is contrary to the intent of C.

If by "produced legally by arithmetic operations", you mean

	char *foo;
	int bar;

	foo = (bar - bar);

that's false.  When K&R refers to null pointers, it always refers to
"the constant 0", specifically excluding anything but the number "0" in
a source program (or #define constant expanding to that number):

	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.

and

	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.

Converting "an object of integral type" to a pointer, and back to
an integer, "always carries an integer converted form a pointer back into
the same pointer", but that merely means that an integer *variable* having the
value 0 must map into a pointer which, when mapped into an integer, maps into
the value 0.  This can be implemented by an bit-for-bit identity mapping
of pointers to integers and vice versa (BTW, I'd be the first to admit
that not only our 16-bit "int", 32-bit pointer 68000 C compiler, but AT&T's
68000 C compiler when built with 16-bit "int"s, and every other 16-bit
"int", 32-bit pointer C compiler, violates this; it implies that an "int"
must have at least as many distinct values as a pointer).

On a machine on which a null pointer had some special non-zero bit value, this
would mean that

	char *p, *q;
	int foo;

	p = 0;
	q = foo - foo;
	if (p != q)
		printf("Not equal\n");

would print "Not equal".  So what?  As stated above, the C Reference Manual
refers to null pointers as being conversions of *the constant* 0 to a
pointer value; since the only integer value which has to be converted to
a null pointer is a constant, there's no need for any run-time checks.

Frankly, I think a C implementation with all-zero null pointers on a machine
which strongly encouraged you to use some special non-zero pattern for
null pointers would violate C's charter much more; it would mean the C
implementation wasn't as close to the machine as it could be.  If the
"native" implementation language on the machine wasn't C, you'd have a real
pain interfacing to it.

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

ron@brl-tgr.ARPA (Ron Natalie <ron>) (10/17/84)

>    The expression can be made portable, at least under UNIX, by
> having
> 	extern end;
> somewhere in a header file, and then replacing "0" in the expression by
> "&end".  For maximum portability, of course, use a real object of type
> struct foo in the expression.

FOO! WRONG!  On non-byte addressed machines this isn't guaranteed to work
either.

-Ron

gwyn@brl-tgr.ARPA (Doug Gwyn <gwyn>) (10/18/84)

If you want to take issue with the producibility of a 0 pointer
through arithmetic operations, please do not invent incorrect
examples and attribute them to me, then shoot them down.  Thanks.

gwyn@brl-tgr.UUCP (10/18/84)

Relay-Version: version B 2.10 5/3/83 based; site houxm.UUCP
Posting-Version: version B 2.10.2 9/18/84; site brl-tgr.ARPA
Message-ID: <5380@brl-tgr.ARPA>
Date: Thu, 18-Oct-84 15:12:29 EDT
Date-Received: Fri, 19-Oct-84 06:31:23 EDT

 <204@rlgvax.UUCP>
Organization: Ballistic Research Lab
Lines: 4


If you want to take issue with the producibility of a 0 pointer
through arithmetic operations, please do not invent incorrect
examples and attribute them to me, then shoot them down.  Thanks.

guy@rlgvax.UUCP (Guy Harris) (10/19/84)

> If you want to take issue with the producibility of a 0 pointer
> through arithmetic operations, please do not invent incorrect
> examples and attribute them to me, then shoot them down.  Thanks.

You did not cite any examples; I was postulating the only one I could
think of.  If you think a pointer which must have the same bit pattern
as a null pointer can be produced by any expression other than

	(something *)0

please exhibit it; my reading of K&R shows no such expression.  Thanks.

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

guy@rlgvax.UUCP (Guy Harris) (10/19/84)

Arithmetic expressions that produce pointers:

1) Purely integer expressions:

As discussed in my previous article, K&R indicates that no such expression,
except a constant 0, is to be interpreted as a null pointer.  The phrase
"the constant 0" appears in several places (in the discussion of the conditional
operator, as well as the places mentioned in my previous article); I do
not think that the modifier "the constant" appears by accident.  I believe
it was explicitly put there to indicate that an arbitrary integral result
of zero need not be converted into a null pointer; only an explicit zero
constant need be so converted.  If somebody has a statement to the
contrary, from either K or R, they should exhibit it.

2) Pointer plus or minus an integer expression:

The actual phrase in "7.4 Additive operators" reads

	A pointer *to an object in an array* and a value of any integeral
	type may be added.  The latter is in all cases converted to an
	address offset by multiplying it by the length of the object
	to which the pointer points.  The result is a pointer of the
	same type as the original pointer, and which points to another
	object in the same array, appropriately offset from the
	original object.

A null pointer does not point to any object in an array.  If
you add an integer to a pointer, by the paragraph above the resulting
pointer points to an object in an array.  Therefore, it is not a
null pointer.

I am quite aware that if you have a pointer to an element in a character
array on a PDP-11, and the element has the address 0177777, adding one
to that pointer yields the result 0.  This is not an argument that you
can produce a null pointer by an arithmetic expression.  First of all, arrays
move forward in memory, so there *is* no next element in that array, as the
element in question is at the end of your address space.  Second of all, if
you have a machine on which a null pointer does not have the value zero,
and you add 1 to a pointer whose value is such that adding 1 to it will
cause wrap-around, you have still not produced a null pointer.  You
may have produced a pointer that doesn't point where it "should", and which
may even to a non-existent part of the address space, but that does not
mean it must be a null pointer.

3) Other expressions:

Under 14.4, "Explicit pointer conversions", it says

	Certain conversions involving pointer are permitted *but have
	implementation-dependent aspects....

	...An object of integeral type may be explicitly converted to
	a pointer.  The mapping always carries an integer converted
	from a pointer back to the same pointer, but is otherwise
	machine dependent.

This implies that if you convert a null pointer to an integer, the
integer that results must convert back into a null pointer.  The most
natural and "unsurprising" conversion (see the previous paragraph in section
14.4 on conversions from pointer to integer) is just a bitwise copy.  If
converting a null pointer produces an integer with the value 0xff000000,
so be it.  If that's how a null pointer is represented internally, I'd
find conversion of a null pointer into a zero integer more surprising than
conversion of it into 0xff000000.  Given that, converting an integer back
into a pointer by a bitwise copy would be the natural way to do it; this
would convert an integer value of 0, other than a constant 0 (which is
*not* an integer converted from a pointer), into a pointer with the value
0, not a null pointer, and would convert an integer with the value 0xff000000
into a null pointer.

Yes, this implies that it's a pain to produce a pointer which points to
location 0.  It even implies that producing a pointer which points to
location 0 can't be done the same way you produce a pointer which points
to location 1; you'd have to say

	something *p;
	int i;

	p = (i - i);

Worse things have happened.  It may be a pain to produce such a pointer,
but it's not impossible, and it's not *that* common an operation.

So what sort of arithmetic expressions are left?

I do rescind my earlier statement that 16-bit "int"s and 32-bit pointers
are illegal.  The statement that "(the integer-to-pointer mapping)
always carries an integer converted from a pointer back into the same
pointer" does not imply that an "int" must be big enough to hold a pointer.
It merely implies that there must be an *integral type* big enough to
hold a pointer; "int" is not the largest integral type, just the most
"natural" type.  "Natural" is not a precise specification; it implies
that the choice of size of "int" is machine dependent.  Of course, what
is most "natural" given the data path width of the machine isn't necessarily
the most "natural" given the size of objects you can put on the machine;
try using "malloc" and "realloc" to grow a symbol table past 64K on
a machine with 16-bit "int"s but 32-bit pointers.  (It can't be done in
a straightforward fashion.  Believe me.  We have such a machine, and we've
*tried*.  The standard UNIX "nm" uses that technique, and if your symbol
table is bigger than 64K bytes, you lose.)  So if you have 32-bit "int"s,
you can't convert the pointer with the bit pattern 0x801234 into an "int"
and back and get the same value back, but you can convert it to the
integral type "long" and back; as it says in section 14.4, paragraph
2,

	A pointer may be converted to any of the integral types *large
	enough to hold it.  Whether an "int" or "long" is required
	is machine dependent.*  (italics mine)

However, it does state specifically that the difference between two pointers
is an "int", not just an integral value.  (We don't do that.  *Nostra
culpa* - not "*mea culpa*"; it wasn't my idea.  Our newer systems will
bite the bullet and have 32-bit "int"s, mainly for compatibility with
our 32-bit supermini, but also because they're 4.2BSD-based,
and there's probably several *months* of work changing 4.2BSD to use "long"
instead of "int" when it means "32-bit quantity".  I assume the AT&T
68000 C compiler gets this right, when built for 16-bit "int"s.)

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

ron@brl-tgr.ARPA (Ron Natalie <ron>) (10/19/84)

> >A problem with expressions like
> >
> >        &((foo *)0)->bar
> > ...
> 
> Perhaps more robust would be  ((char *)&((foo *)0)->bar - (char *)(foo *)0)
> 
> so that, regardless of the value of ((foo *)0), all that is expected of the
> compiler is that &((foo *)0)->bar be produced by adding the offset to bar
> to the value of ((foo *)0).  There is still the presumption that any pointer
> can be converted to (char *) without loss of information.

But nowhere does the spec say that saying 0->anything or *(pointer_cast)0
is guaranteed to valid at all.  Zero can be put into a pointer so that it
can be checked for later, period.  On certain architectures (like ones that
have different pointer layouts for various types) trying to 0-> something
may not be valid.  Consider the HEP:  The pointers work as follows:

      Char    SHORT   Medium  int (and long)
	0	1	2	0
	1
	2	3
	3
	4	5	6
	5
	6	7
	7
	8			8

In these cases something that begins with short or medium would have
a zero pointer of 1 or 2 even though the compiler traps the special
NULL (0) case and handles it.

gwyn@brl-tgr.ARPA (Doug Gwyn <gwyn>) (10/19/84)

Although I see no particular use for the following piece of code,
I believe that it is supposed to be legal both now and in the ANSI
standard:

	char	*foo;

	foo -= (long)foo;	/* or (int) perhaps */
	/* foo now is NULL */

More importantly,

	char	*malloc(), *where;

	where = malloc( (unsigned)40 );

	subr( where );	/* check validity of `where' and does something */

Here a NULL pointer must be type-compatible with other pointers.  Using
a special "nil" pointer could easily get in the way here (subr() might
end up with special-case "nil"-handling in-line code every time its
parameter is used).

What more does using special "nil" get you than using a 0, apart from
hardware check against dereferencing NULL?  In any case, the hardware
check is unnecessary if you write your code correctly.  Are we all
hackers or are there some professional programmers out there?

gwyn@brl-tgr.UUCP (10/19/84)

Relay-Version: version B 2.10 5/3/83; site houxe.UUCP
Posting-Version: version B 2.10.2 9/18/84; site brl-tgr.ARPA
Message-ID: <5400@brl-tgr.ARPA>
Date: Fri, 19-Oct-84 16:19:34 EDT

 <204@rlgvax.Fri, 19-Oct-84 13:19:34 PDT
Organization: Ballistic Research Lab
Lines: 26

Although I see no particular use for the following piece of code,
I believe that it is supposed to be legal both now and in the ANSI
standard:

	char	*foo;

	foo -= (long)foo;	/* or (int) perhaps */
	/* foo now is NULL */

More importantly,

	char	*malloc(), *where;

	where = malloc( (unsigned)40 );

	subr( where );	/* check validity of `where' and does something */

Here a NULL pointer must be type-compatible with other pointers.  Using
a special "nil" pointer could easily get in the way here (subr() might
end up with special-case "nil"-handling in-line code every time its
parameter is used).

What more does using special "nil" get you than using a 0, apart from
hardware check against dereferencing NULL?  In any case, the hardware
check is unnecessary if you write your code correctly.  Are we all
hackers or are there some professional programmers out there?

guy@rlgvax.UUCP (Guy Harris) (10/20/84)

> What more does using special "nil" get you than using a 0, apart from
> hardware check against dereferencing NULL?  In any case, the hardware
> check is unnecessary if you write your code correctly.  Are we all
> hackers or are there some professional programmers out there?

Assuming you are handed an existing machine and an existing OS, libraries,
and language tools with hardware and/or software conventions assigning a non-zero
value to a null pointer, it gets you compatibility between a C implementation
done for that machine and the other languages on that machine.  If the hardware
check is already there, one might as well use it.  There's plenty of crap
code out there - in standard versions of UNIX - that *does* dereference null
pointers; hardware/software that catches it can help flush it out.  We
discovered this in our port to our 68000-based micro, which shoots down
null pointer dereferences for reasons other than checking code.  Also note
that even the best programmers do not produce 100% perfect code every time;
no amount of methodology or care can eliminate all errors, as the programmer
could easily err in applying their checks as easily as they err in producing
code.

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

henry@utzoo.UUCP (Henry Spencer) (10/22/84)

> Although I see no particular use for the following piece of code,
> I believe that it is supposed to be legal both now and in the ANSI
> standard:
> 
> 	char	*foo;
> 
> 	foo -= (long)foo;	/* or (int) perhaps */
> 	/* foo now is NULL */

This falls down if pointers are not byte indexes, e.g. if the byte info
is up in some of the high-order bits.  (I think the pdp10 does that.)
Remember that the ptr--->long conversion is not required to produce a
sensible number.  There is also a more subtle assumption here, that memory
is in fact organized as an array of bytes, so subtracting the "position"
of a byte pointer from the pointer gives 0.  (I can't immediately think
of a counterexample, but it's not ruled out.)  Finally, nowhere does the
ANSI standard (or K&R) say that *any* computed pointer value, regardless
of the computation, must compare equal to integer constant 0 (i.e. NULL).
The only way to get a pointer that is guaranteed to compare equal to NULL
is to derive it from an explicit NULL.

> 	char	*malloc(), *where;
> 
> 	where = malloc( (unsigned)40 );
> 
> 	subr( where );	/* check validity of `where' and does something */
> 
> Here a NULL pointer must be type-compatible with other pointers.  Using
> a special "nil" pointer could easily get in the way here (subr() might
> end up with special-case "nil"-handling in-line code every time its
> parameter is used).

Why?  If subr() compares the pointer to an explicit NULL, then that
comparison is done against the special "nil" value.  If the comparison
is against a pointer derived from an explicit NULL, same thing.  If the
comparison is against anything else, there are no promises made by the
ANSI draft or K&R.  The choice of bit pattern to represent NULL is
entirely irrelevant.

> What more does using special "nil" get you than using a 0, apart from
> hardware check against dereferencing NULL?  In any case, the hardware
> check is unnecessary if you write your code correctly.  Are we all
> hackers or are there some professional programmers out there?

Among other things, on some "smart" pieces of hardware, you cannot even
load some bit patterns into a "pointer" register without getting a trap.
On such hardware, you can't use such a bit pattern to represent NULL
without all kinds of hassles when manipulating might-be-NULL pointers.
On at least one such machine that I've heard of, all-zeros is one of the
bad bit patterns.
-- 
				Henry Spencer @ U of Toronto Zoology
				{allegra,ihnp4,linus,decvax}!utzoo!henry