0 From: chris@mimsy.umd.edu (Chris Torek) Organization: U of Maryland, Dept. of Computer Science, Coll. Pk., MD ~n74~ Den hŠr artikeln kommer ursprungligen frŒn USENET. Vi har behŒllit den pŒ original- sprŒket som en liten švning. Speciellt ni som tŠnker gŒ Lysators C-kurs bšr lŠsa och he~runda den. You may wish to save this, keeping it handy to show to anyone who claims '#define NULL O is wrong, it should be #define NULL '.I intend to do so, at any rate. Let us begin by postulating the exis- tence of a machine and a compiler for that machine. This machine, which I will call a 'Prime', or sometimes 'PRIME', 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() fl (NULL);/* wrong */ f2 (NULL);/* wrong */ exlt (O), :1 (cp) har *cp; z (dp) ,uble *dp; There are two lines marked 'wrong'. Now suppose we were to define NULL as 0. Clearly both calls are then wrong- both pass ' (int)O', when the first should be a 48 bit (char *) nil pointer and the sec- ond 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 ( char *) 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 a$ (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 defi- niŒon of NULL: main() fl((char *)O); f2((double *)0); exit(O); 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 O we can then replace the two 'o's with 'NULL's: fl((char *)NULL); f2((double *)NULL); exit(O); The preprocessor changes both NULLs to 0s, and the code remauns correct. On a machine such as the hypothetical 'Prime', there is no single definition of NULL that will make uncasted, unproto- typed arguments correct in all cases. The C language provides a reasonable means of making the arguments correct, but it is not via '~define'. C's untyped nil pointer, which MUST oe given a type before it can be used cor- rectly, is written as '0' (and 'OL', and possibly using constant integer expres- sions, depending on whose definition you use; but ' 0 ' suffices and must work). After it has been given a type ('(char *)O') it becomes a nil pointer of that type. Once it has a type (if we ignore some fine points in the dpANS, many of which are unlikely to be implemented in curt-ent C compilers) it may not be used as a nil pointer of another type. Hence (char *)O is a nil pointer to char, and as such may not be used as a nil pointer to int, or a nil pointer to struct tcurts, or indeed as anything other than a pointer to char. It may work -- indeed, it is more likely to work than to fail -- but it is incorrect and unportable, and should (and does in pcc) draw at least a warning from the compiler. There are only two ways that the untyped nil pointer can acquire a type, namely assignment and comparison. Casts are a special case of assignment, as are arguments to functions that have pro- totypes in scope. Where this causes the most trouble is in arguments to functions thal do not have prototypes in scope, or for which the prototype does not specify a type for that argument: e.g., execl~): void execl~char *, ...); The only correct way to fill in the blank is with ~char *)O (or possibly ~char *)OL and similar tricks; outside of obtuscated C contests, these tricks are not worth considering). The dpANS has at present one more instance of an 'untyped' nil pointer, name- ly '(void *)0'. The differences between using 'O' and ' (void *)O' as a 'generic nil' are, first, that while O is also an integer constant, (void *)O is not, and second, that (void *)O is also a typed nil pointer (ouch! -- more below). Suppose that NULL is defined as either 'O' or ' (void *)O' -- one of the two untyped nil pointers -- but that we do not know which one. Which of the following calls are correct? /* definitions before the * fragments ~note lack * of prototypes) */ void fl(cp) char *cp; { } void f3~vp) void *vp; { . . fl(NULL); It is easy to see that calls 2, 4, and 6 (which cast their arguments and hence provide types) are correct. The surprise is that while calls 1, 3, and 5 are all wrong if NULL is defined as 'O', calls I and 5 are ooth correct, or at least will both work, if NULL is defined as ' ~void *)0'. Ca~ 3 is wrong in any case. We can get away with 'fl((void *)O)' only because of a technicality: the dpANS says that (void *) and (char * ) must have the same representation (which more or less means 'must be the same type'), and because (void *) is a valid pointer type, (void *)O must be a valid nil pointer of type 'void *', and thus must also be a valid nil pointer of type 'char *'. (Actually, this argument glosses over a subsidiary technicality, in that there is no guarantee that there is only ONE valid nil pointer of any given type, but that way lies madness. There are more arguments about whether 'same representation' implies 'indistinguish- able'; these, too, are best left untouched.) There are no ANSI-conformant C com- pilers, for there is as yet no ANSI C stan- dard. One should therefore assume that code may have to run under a compiler where NULL is defined as ' 0 ', not as ' (void *) 0', and should therefore avoid calls like 1, 3, and 5 above. In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept +1 301 4547163 Domain:chris@mimsy.umd.edu Path:uunet!mimsy!chris