NULL v. zero
Posted Jul 22, 2004 18:59 UTC (Thu) by
h.j.thomassen (guest, #15232)
Parent article:
NULL v. zero
I would like to bring this discussion back to a plain vanilla C one.
It may help if you read K&R, 2nd Ed. page 102:
"Pointers and integers are not interchangeable. Zero is the sole exception: the constant zero may be assigned to a pointer, and a pointer may be compared with the constant zero. The symbolic constant NULL is often used in place of zero, as a mnemonic to indicate that this is a special value for a pointer"
The first edition has a similar text on page 97/98. So: NULL is a mnemonic to support readability: nothing more..
Hence the usual #define NULL 0 [And not: (void *)0 or (char *)0].
Statements like "ptr = 0" or "if (ptr != 0)" are perfectly legal C, but K&R have introduced NULL for better readability. Linus is right to follows K&R in this.
The use of NULL instead of 0 shows your collegues that you are aware that you used an exceptional language feature. But automated compiler checks are out of the question.
If a subroutine wants a ptr-argument, it is wrong to write "subr(0)", because it is not within the limits of the above K&R wordings. But "subr(NULL)" is equally wrong. In ANSI C the function-prototyping mechanism will "fix" this error for you on the fly. In pre-ANSI C, or in ANSI-C without a known prototype for your subr, you are lucky (but buggy nevertheless) if "sizeof(0)==sizeof(your_ptr)". Your program blows if this is not the case. I have seen a lot of this in the days when the first Motorola68000 C-compilers had the habit of giving two bytes to an int, and four bytes to a pointer.
But the comment by Kamil (above) with the "execl" example hits an exceptionally sore spot, since even the ANSI-prototype mechanism can not save you if the prototype is declared with a variable number of arguments. If you pass a 0 or a NULL as the argument to an execl, you get "sizeof(int)" zero-bytes on the argument stack. The subroutine expects "sizeof(const char *)" zero-bytes as a terminator. On modern 32-bit compilers those two sizes happen to be equal. But there is nothing in the C-language that requires them to be equal. The execl example really *must* have "(const char *)NULL" (or (const char *)0) to be theoretically correct in size.
Beware: history may repeat. The Motorola68000 example is from the days that we converted from 16-bit thinking to 32-bit thinking. The conversion from 32-bit to 64-bit thinking may uncover lots of similar problems.
And, by the way, there is nothing in the C standard that says that all pointers should be of equal size. I have seen one CPU-with-C-compiler where sizeof(int *) was 2, and sizeof(char *) was 3. Admittedly, this was 25 years ago.
But it is the reason why void-pointers were introduced in the first place.
The guarantee that the language gives you is that sizeof(void *) >= sizeof(any_other_data_ptr_type). The void-pointer was introduced because the language needed a "pointer-transport-crate", suitable to store (cast) any other type/size of data pointer into, without the risk of loosing precision.
From a theoretical point of view the above "execl" example might receive too many zero-bytes if you write (void *)0 instead of (char *)0. The (char *)0 is what the execl-prototype requires, and therefore the only correct pointer-type (and size!).
I think that the world would collapse if a compiler would come up now where not all pointers, including the void pointer, would have equal size; but that's a different topic.
Hendrik-Jan Thomassen
(
Log in to post comments)