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