[comp.sources.d] C Pointers and Integers

bart@reed.UUCP (Bart Massey) (07/21/88)

In article <3027@geac.UUCP> david@geac.UUCP (David Haynes) writes:
> In article <19829@watmath.waterloo.edu: rwwetmore@grand.waterloo.edu (Ross Wetmore) writes:
> :In article <1305@ucsfcca.ucsf.edu: root@cca.ucsf.edu (Computer Center) writes:
> ::In article <19789@watmath.waterloo.edu:, rwwetmore@grand.waterloo.edu (Ross Wetmore) writes:
[ *lots* of stuff about C pointers and integers ]

Yes, I know I'm stupid to get involved in this religious war.  However, K&R2
contains a detailed discussion on the subject of pointers and integers --
let's take a look at it, and see what the two most popular C standards say
about the issues discussed above.  In particular, if you want the short
answer to the previous discussion, skip to the third paragraph of text
quoted below.

---------------------
K&R2, page 198-99, section A6.6, "Pointers and Integers":

	"An expression of integral type may be added to or subtracted from a
	pointer; in such a case the integral expression is converted as
	specified in the discussion of the addition operator (A7.7)."

Section A7.7 basically says that adding an integral to a pointer gives the
nth object off that pointer, except you can't index more than one off the
end of the storage allocated for the pointer.

	"Two pointers to objects of the same type, in the same array, may be
	subtracted:  the result is converted to an integer as specified in
	the discussion of the subtraction operator (A7.7)."

Note that A7.7 actually says that the result in ANSI C is *not* necessarily
an integer, but is of type "ptrdiff_t" from the standard header <stddef.h>.
Sigh.  I interpret these as meaning that the result must be of some integral
type, but I suppose one could insist that K&R2 conflicts, and thus no such
requirement can be made.  Note that all of this is a change from K&R section
7.4, where the result *was* in fact guaranteed to be an integer.

	"An integral constant expression with value 0, or such an expression
	cast to type void *, may be converted, by cast, by assignment, or by
	comparison, to a pointer of any type.  This produces a null pointer
	that is equal to anoter null pointer of the same type, but unequal
	to any pointer to a function or object."

This *explicitly* answers the question "is 0 'the same' as (char *) 0."  The
correct answer is "not internally, but a valid K&R2 compiler is required to
hide any difference from the programmer by doing implicit casts."  Note that
this is *not* a change from K&R (7.7 and 7.14), that *any* pointer type is
compatible with 0, and that 0 is a special case:  no other integer constant
is implicitly equivalent to any pointer.  A good way to think of it is that
when used in a pointer context, the symbol 0 is like the Pascal nil, instead
of like an integer.

	"Certain other conversions involving pointers are permitted, but
	have implementation-dependent aspects.  They must be specified by an
	explicit type-conversion operator, or cast (A7.5 and A8.8)."

	"A pointer may be converted to an integral type large enough to hold
	it; the required size is implementation dependent.  The mapping
	function is also implementation dependent."

	"An object of integral type may be explicitly converted to a
	pointer.  The mapping always carries a sufficiently wide integer
	from a pointer back to the same pointer, but is otherwise
	implementation dependent."

Note that these claims really don't seem to *require* anything of the
compiler, but simply *allow* some traditional behaviors.  It is an
interesting question whether an integral type "sufficiently wide" or of "the
required size" is required to exist in all implementations.  In my opinion,
probably not.

	"A pointer to one type may be converted to a pointer to another type.
	The resulting pointer may cause addressing exceptions if the subject
	pointer does not refer to an object suitably aligned in storage.  It
	is guaranteed that a pointer to any object may be converted to an
	object whose type requires less or equally strict storage alignment
	and back again without change; the notion of "alignment" is
	implementation-dependent, but objects of the char types have the
	least strict alignment requirements.  As described in A6.8, a
	pointer may also be converted to type void * and back again without
	change."

void * is the widest pointer type.  char * is the widest pointer type as
well.  Thus, both "(foo *) (char *) x" and "(foo *) (void *) x" are
guaranteed to give x for any valid x of type foo *.  Note that the result of
a cast even to char * or void * may not necessarily be *dereferencable* as
is, just convertible back to its original type without loss of generality.

	"Finally, a pointer to a function may be converted to a pointer to
	another function type.  calling the function specified by the
	converted pointer is implementation dependent; however, if the
	converted pointer is reconverted to its original type, the result is
	identical to the original pointer."

All function pointers are of equal width.

------------------

Hope this helps -- flame away!! :-) :-) :-) :-)

					Bart Massey
					UUCP: ..tektronix!reed!bart

plocher@uport.UUCP (John Plocher) (07/24/88)

>[ *lots* of stuff about C pointers and integers ]
>
>Yes, I know I'm stupid to get involved in this religious war.  However, K&R2
>contains a detailed discussion on the subject of pointers and integers --

I shouldn't do this, but...

Thanks for the overview - I think that most (if not all) the compilers on
intel 286 machines (one of the more prolific 32/16 bastards out there :-)
handle this without problems.  One *can* do stuff like:

	double *dp;
	...
	if (dp == 0) ...

But because C does no checking of function arguments (K&R C, not ANSI C, for
this example) the following will fail on a machine where sizeof(int) !=
sizeof(pointer):

	void foofunction( i, p, ii )
	int i, ii;
	double *p;
	{
	    if (p)
		printf("%d %f %d\n", i, *p, ii);
	    else
		printf("%d %d\n", i, ii);
	}

	main() 
	{
		foofinction( 1, 0, 2 );
	}

The problem (which is addressed by function prototypes in ANSI C) is that
the compiler doesn't have type info for the 2nd parameter to foofunction().
i.e., what size should the 0 be converted to when it is pushed on the stack
(or how many registers should it use...)

It is in this case that you *MUST* cast 0 to (char *) 0 or (double *) 0.
Since NULL is defined to be 0, you can also type this as (char *) NULL.

Since K&R define NULL as 0, implementations which define it to anything
else are wrong:
	#ifdef LARGE_MODEL
	# define NULL	0L	/* or (char *)0 */
	#else
	# define NULL	0
	#endif
This is the most common (bad) example I've seen in the intel CPU world.

	-John Plocher