bill@proxftl.UUCP (T. William Wells) (07/03/88)
There is a very common confusion about zero and null pointers, that they are the same thing. This has been reenforced by the fact that on many machines this assumption will always work. Zero is not the same thing as a null pointer. Both K&R and X3J11 have language which asserts that: Assigning a constant zero to a variable is equivalent to storing a null pointer of the appropriate type there. Comparing a pointer to a constant zero is equivalent to comparing with a null pointer of the appropriate type. Casting a constant zero to a pointer type creates a null pointer of the appropriate type. It does not say ANYWHERE that the integer (or long) constant zero and a given null pointer are the same. It also does not say that any other zero than a constant zero can be used for these assignments. These are very subtle differences. On many machines, a null pointer is represented by a bit pattern of all zeros. So also is zero. Thus, you will see code like: struct foo { char *x; } bar; zero(&bar, sizeof(bar)); which assumes that x is now the null pointer. It is not. Instead, x has been initialized to a bit pattern of all zeros. On a machine where the null pointer is represented by a bit pattern of zeros, the code will work correctly. On other machines, anything can happen. So, what does this mean for the C programmer? Constructs like: foo *p; bar *q = 0; bar *r = (bar *)0; p = 0; p = (foo *)0; if (p) ... /* equivalent to if (p != 0) ... */ if (p == 0) if (p == (foo *)0) if (p != 0) if (p != (foo *)0) work because they involve the operators for which the conversions from a constant zero to a null pointer is valid. Constructs like: foo *p; int x; x = 0; p = x; x = 0; p = (foo *)x; x = 0; if (p == x) ... x = 0; if (p == (foo *)x) ... x = 0; if (p != x) ... x = 0; if (p != (foo *)x) ... do not work. The examples without casts fail because the conversion from an integer to a pointer is illegal in C, your compiler should give you a compile-time error message. The examples with a cast do not work because they produce a pointer to address zero, which MIGHT happen to also be the address used to represent a null pointer, but which might not. One final gotcha for C programmers. This code is broken: === { func(0); } func(p) bar *p; { === } Here is why: the programmer intended that a null pointer be passed as the value of bar. However, what he actually passed was a value equal to zero. Now, should the compiler have null pointers whose representation has the same bit pattern as the value zero, all will be well (and this is true for many machines); but should they not, some random pointer value will get passed. If one is using Standard C, one should use prototypes for functions, this fixes the problem. If not, a zero which is being passed to a function and which is intended to be converted to a null pointer should always be explicitly cast to the null pointer. With this in hand, it is now possible to say what the value of NULL should be. First, NULL is intended as something which can be used to represent a null pointer. This is NOT possible because there is no such thing as THE null pointer. The best that can be done is to have something which can be used where an unadorned zero would be valid: as an operand to assignment, comparison, or cast. This means that the proper value for NULL is zero, or, possibly, for Standard C, ((void *)0). Note that this does not fix the problem with functions; calling func(NULL) is as wrong as calling func(0). If NULL is defined as 0, they are equivalent. If NULL is defined as ((void *)0), there is nothing that guarantees that the ((void *)0) has the same representation as bar *. A personal note: I do not use NULL at all, because I do not think the confusion created by its use is worth the minimal or nonexistent improvement in program clarity.
beckenba@cit-vax.Caltech.Edu (Joe Beckenbach) (07/07/88)
In article <402@proxftl.UUCP> bill@proxftl.UUCP (T. William Wells) writes: >There is a very common confusion about zero and null pointers, >that they are the same thing. This has been reenforced by the >fact that on many machines this assumption will always work. `0' cast to pointers makes a null pointer of that type. This means that NULL === (cast) 0. Standard practice is to say NULL === 0, then cast the typeless. [Or just ignore it, since the compiler is `supposed to cast things correctly anyway' :-| ] This echos Mr. Wells' article. >Note that this does not fix the problem with functions; calling >func(NULL) is as wrong as calling func(0). If NULL is defined as >0, they are equivalent. If NULL is defined as ((void *)0), there >is nothing that guarantees that the ((void *)0) has the same >representation as bar *. > >A personal note: I do not use NULL at all, because I do not >think the confusion created by its use is worth the minimal or >nonexistent improvement in program clarity. Given all the above, I have decided long since to let the compiler do all the worrying about the actual bits. Another poster has also pointed out that THE null pointer does not exist, simply a null pointer for each type. My encapsulization of the whole mess: ( previously given #define NULL 0 [or whatever] ) #define NIL(type) (type) NULL and never-after do I even think of NULLs, only NIL pointers of the appropriate type. This dramatically improves clarity to my eye, and solves the typeless-constant problem to boot. -- Joe Beckenbach beckenba@csvax.caltech.edu Caltech 1-58, Pasadena CA 91125 Mars Observer Camera Project Caltech Planetary Sciences Division Ground Support Engineering, programmer