NULL v. zero
Linus responds that programmers who interchange NULL and zero are confused about the types they are using and are putting that confusion into the kernel. In his desire to enable the compiler (and other compile-time checkers) to find errors, he wants to separate the integer and pointer types as completely as possible. NULL is a pointer, while 0 can never be.
char * p = 0; /* IS WRONG! DAMMIT! */ int i = NULL; /* THIS IS WRONG TOO! */
and anybody who writes code like the above either needs to get out of the kernel, or needs to get transported to the 21st century.
One might conclude from this statement that Linus is pretty well convinced
that the current course of action is correct. He also states that, without exception, changing zero
to NULL has resulted in better, more readable code. So use of
NULL seems to have become part of the official kernel coding
style, even if the CodingStyle document is
still silent on the matter.
Index entries for this article | |
---|---|
Kernel | Coding style |
Kernel | NULL and zero |
Posted Jul 15, 2004 11:38 UTC (Thu)
by ikm (guest, #493)
[Link] (26 responses)
1) as of the language: an integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant. If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function. [6.3.2.3.3] 2) as of the libraries: NULL is a macro that expands to the implementation-defined null pointer constant. [7.17.3] These two statements clash a bit -- the null pointer constant is not an implementation defined, but rather a standards-defined. It is totally correct to use 0 to initialize pointers to zero. That's what the standard states. With all due respect, Linus should have read the C standard before stating the contrary.
Posted Jul 15, 2004 11:46 UTC (Thu)
by ikm (guest, #493)
[Link]
Posted Jul 15, 2004 12:15 UTC (Thu)
by dougm (guest, #4615)
[Link] (6 responses)
Posted Jul 15, 2004 15:52 UTC (Thu)
by ikm (guest, #493)
[Link] (5 responses)
By the way, are there some real examples when the use of 0 instead of NULL would lead to some real error? I'm just curious.
Posted Jul 15, 2004 17:03 UTC (Thu)
by kamil (guest, #3802)
[Link] (4 responses)
int execl(const char *path, const char *arg, ...); Here, one should pass null pointer as the last argument. Passing 0 is usually OK on 32-bit machines, but not on 64-bit ones. You must explicitly cast 0 to a pointer type to conform to the standard. But NULL will be just as erroneous here, given that the standard allows it to be defined as a plain int 0. So you would in fact have to write (void*)NULL to conform both to the C standard and to Linus :-).
Posted Jul 15, 2004 19:25 UTC (Thu)
by knobunc (guest, #4678)
[Link] (1 responses)
-ben
Posted Jul 15, 2004 19:34 UTC (Thu)
by Ross (guest, #4065)
[Link]
Posted Jul 15, 2004 19:32 UTC (Thu)
by Ross (guest, #4065)
[Link]
Posted Jul 15, 2004 22:45 UTC (Thu)
by ikm (guest, #493)
[Link]
Of course, all other values for NULL would be incorrect, as it was explained in my initial posting. One can not define NULL as 0xFFFFFFFF, for example, as it would not qualify as a null pointer constant. Thanks for providing the example, that clarified things a bit!
Posted Jul 15, 2004 13:34 UTC (Thu)
by elanthis (guest, #6227)
[Link] (9 responses)
A code checker like Sparse will often enforce a stricter dialect than standard C, which is a good thing, because then it will find errors that the normal C compiler wouldn't pick up. Correct type checking is one such error. It also does improve readability. If you are skimming through some code, and see: if (var == 0) { ... } You are likely to assume var is an integer, no? However, if you see: if (var == NULL) { ... } You are likely to assume var is a pointer, yes? Using the appropriate type makes the code easier to read and understand.
Posted Jul 15, 2004 15:41 UTC (Thu)
by ikm (guest, #493)
[Link] (8 responses)
In case it was really about coding styles and not about the language, this is all moot anyway. People could use 0 as NULL, and it would be perfectly legal and correct, as long as they don't participate in kernel development that way. Sorry guys, I overlooked that. Btw, the quoted "int i = NULL" is always incorrect, and that's the language issue, not a style issue.
Posted Jul 15, 2004 18:23 UTC (Thu)
by dvdeug (guest, #10998)
[Link] (1 responses)
Posted Jul 15, 2004 23:03 UTC (Thu)
by ikm (guest, #493)
[Link]
Posted Jul 15, 2004 19:37 UTC (Thu)
by Ross (guest, #4065)
[Link] (2 responses)
Valid C: int *ptr=malloc(10*sizeof(int)); That's not valid C++. Instead you have to do this: int *ptr=(int *)malloc(10*sizeof(int)); Which is really annoying because it is obvious from the context what the So in C++ the nil pointer is 0 (they are very adamant about this).
Posted Jul 15, 2004 20:20 UTC (Thu)
by dvdeug (guest, #10998)
[Link] (1 responses)
Posted Jul 15, 2004 23:52 UTC (Thu)
by dododge (guest, #2870)
[Link]
The header inclusion issue is in C, not C++. The differences in the languages mean that there is no single way to call malloc that works well for both of them.
If you're writing C++ you have to cast malloc because the compiler won't like the implicit conversion. If you're writing C you shouldn't cast it, in order to force the compiler to warn you if there's no prototype in scope.
Where you can run into trouble is when you've got code or a programmer moving betwen the two languages.
Posted Jul 15, 2004 19:40 UTC (Thu)
by Ross (guest, #4065)
[Link] (2 responses)
int bob; bob = NULL*NULL; // NULL is an integer here but a warning would be nicer nancy = NULL*5; // So you say NULL is a pointer here but I don't actually believe it
Posted Jul 15, 2004 22:02 UTC (Thu)
by sir99 (guest, #3286)
[Link] (1 responses)
AIUI, the lhs doesn't determine how the rhs is evaluated. The rhs is evaluated independently and then promoted to the type of the lhs.
Posted Jul 16, 2004 19:35 UTC (Fri)
by Ross (guest, #4065)
[Link]
That was my point, really. That's why NULL as (void *)0 is better than just
Posted Jul 15, 2004 19:43 UTC (Thu)
by Ross (guest, #4065)
[Link] (6 responses)
(C++ people would disagree and say that 0 is the correct value for the
Posted Jul 15, 2004 22:33 UTC (Thu)
by ikm (guest, #493)
[Link] (5 responses)
Well, as a C++ person I would say that I personally prefer to use 0 everywhere, since it is faster to type, it eats less screen estate, and with all that there was not a single case in my practice where it led to any problem. I guess it's just because C++ libraries and everything I write myself is strictly typed, so the compiler always knows what I want to mean by using 0, and all the problems with the incorrect types are catched when I actually try to use the variables I initialized, not when I initialize them. While I'm quite firm in it for C++, pushing that principle to a lower-level C would be a bit rough, as there are places when the type checking is bypassed, like the execl case demonstrated above (thanks for the demonstration, kamil!)
Posted Jul 16, 2004 19:40 UTC (Fri)
by Ross (guest, #4065)
[Link] (4 responses)
Posted Jul 16, 2004 20:15 UTC (Fri)
by ikm (guest, #493)
[Link] (3 responses)
There is no magic. A zero constant may be implicitly casted either to a null pointer, or to a zero integer. The compiler deduces which cast is required judging by the target type. An integer can not be implicitly casted to a null pointer, as well as any non-zero integer constant. Only a zero integer constant may be casted to a pointer. It is quite simple really. When the target type is ambiguous (e.g. with overloaded functions), the compiler will stop with an error. In this case you will have to cast your zero either to an integer type or to a pointer type explicitly. This behaviour is common with overloaded functions, and is nothing special for pointers.
Posted Jul 17, 2004 4:32 UTC (Sat)
by Ross (guest, #4065)
[Link]
Posted Jul 17, 2004 4:41 UTC (Sat)
by Ross (guest, #4065)
[Link] (1 responses)
char *bob=0*0; would not compile as 0*0 is an integer expression. The implicit conversion Then I retract my statement about the compiler not being able to warn in
Posted Jul 17, 2004 11:08 UTC (Sat)
by ikm (guest, #493)
[Link]
char * bob = 0*0; will compile. Everything that evaluates to a zero at compile time will work. That's what I actually meant by saying "zero constant". The fact that it is possible to assign various constant expressions with the zero result to pointers looks like a misfeature, but it is minor at best. On the other hand, int i = 0; char * bob = i; will not compile, as 'i' does not qualify as something that evaluates to a zero at compile time.
Posted Jul 23, 2004 15:44 UTC (Fri)
by Nelson (subscriber, #21712)
[Link]
More importantly, regardless of whether or not the languages allows it, this is a discussion about style. Linus made the call, don't use zero in place of NULL
Posted Jul 16, 2004 22:59 UTC (Fri)
by jabcslwn (guest, #11815)
[Link] (2 responses)
Posted Jul 16, 2004 23:50 UTC (Fri)
by giraffedata (guest, #1954)
[Link]
I believe C was designed to allow "if (!a)" for the case that a is a logical (boolean) quantity. That it can be used with pointers is a byproduct. I believe the concept of the null pointer came later.
However, I accept "if (!a)" where a is a pointer, because a pointer which may have the null pointer value is in fact two pieces of information in one, and one of them is logical. a having a non-null-pointer value is the logical proposition that a exists. Looked at that way, "if (a == NULL)" is actually harder to read, and not because it uses a few more characters. Because it presents the absence-of-value NULL as if it were an actual pointer value.
Of course, one thing that will always raise my hackles is "if (!a)" where a is a number.
Posted Jul 17, 2004 4:33 UTC (Sat)
by Ross (guest, #4065)
[Link]
Posted Jul 22, 2004 18:59 UTC (Thu)
by h.j.thomassen (guest, #15232)
[Link] (1 responses)
"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. 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. 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
Posted Jul 23, 2004 14:36 UTC (Fri)
by vivi48 (guest, #6412)
[Link]
or (void *)NULL or (void *)0 because the spec explicitely says that with va_arg you can read back a void * as a char *.
Posted Jul 26, 2004 16:41 UTC (Mon)
by h.j.thomassen (guest, #15232)
[Link]
<quote:> A pointer to void shall have the same representation and alignment (ANSI X3.159-1989 has this same text in para. 3.1.2.5 [25]) Also: the va_arg description (para 7.15.1.1) has a discussion about Thanks for pointing :-) this out.
The ISO/IEC 9899:1999 standard states thatNULL v. zero
It is obvious therefore that NULL is always #defined as 0 or ((void *)0).
[In the last paragraph: to null, not to zero, of course.]
NULL v. zero
Linus isn't arguing that it violates the standard; he's arguing that it's a style violation that leads to decreased code readbility and mistakes. This is clear if you read the whole thread.
NULL v. zero
Thanks for the correction. Style coding standards may be more strict than the language itself, of course.NULL v. zero
0 is not good enough if the compiler can't guess from the context that you want a pointer. This typically happens in cases such as this one:NULL v. zero
Sorry, I don't see why you would need to cast NULL to conform to the spec as summarized above since it is says "implementation-defined null pointer constant" so it must provide something that is a 64-bit pointer.NULL v. zero
I think the other person mean (const char *)NULL, but maybe not.
NULL v. zero
Yeah, but unfortunately you can't pass plain NULL either (though it worksNULL v. zero
on almost any system). You have to cast it to the correct pointer type.
(Of course using 0 is even worse.)
I guess you can always safely pass NULL, as it would be declared as a plain 0 only in case it really physically is a plain 0. In any other case it would be ((void *)0). Any sane library would always declare NULL as ((void *)0), because it is the compiler that always knows the actual physical value for sure :)NULL v. zero
The C compiler and standards will accept a lot of things that are garbage, in all honesty. Just because a NULL pointer is just 0 is really nothing more than an implementation detail. It's also an artifact of C's weak type checking, which is one argument people often use in favor of C++, Java, C#, etc.NULL v. zero
The fact that a NULL pointer is just 0 is not an implementation detail. It is not really a value -- it is a standard *syntax* to say that you want a pointer to be null. Please read my comment a bit more thourough, it is all there. It has nothing to do with weak type checking either. As a matter of fact, this works the very same way in C++, that is, null pointer constant is 0. In C/C++, the compiler always knows what you mean, judging from the context (is it a pointer being assigned or an integer? the compiler knows).NULL v. zero
Yes, it is weak type checking. The only way C++ is a strong type checking language is if you compare it to C or Assembly. If you used Ada or Java, or any of a hundred different languages, you couldn't convert a constant of type integer to a pointer. 0, standing alone in a strong typing system is not ambigious, and it doesn't change type depending on what things you stick around it.
NULL v. zero
It is not a weak type checking, it is the absence of any type checking at all is some cases. It is not the language itself which imposes this absence, it is libraries that do. All that ellipsis functions for instance, as an execl example above. These prototypes doesn't give the compiler any clue about the types at all, so it can't apply any type checking in these cases. In case the compiler knows about types, it will act accordingly, converting zeroes to null pointers when applicable.
NULL v. zero
Actually, no. C++ is a little more strict. C++ doesn't have a genericNULL v. zero
pointer type like C.
type should be, and it masks the bug of not including stdlib.h before
calling malloc.
In C it is NULL, which can be either (void *)0 or 0. The first is
obviously better since the compiler will complain if it is used in
invalid contexts but it's a quality of implementation issue not a
conformance issue.
Actually, C++ won't compile if you don't have a function prototype for malloc in scope. If you don't include <stdlib.h> or something else that includes a malloc function, it simply won't work.
NULL v. zero
NULL v. zero
Is it not strange for the language to use the lhs to determine how toNULL v. zero
evaluate the rhs? This always bothered me about C++. For example:
void *nancy;
If NULL is defined as (void*)0, then neither of those is legal C++. It seems that gcc special-cases NULL so that it can be treated as both an integer and a pointer. Further, only an expression that evaluates to 0 at compile-time can be implicitly cast to a pointer; no other integer can be.NULL v. zero
"AIUI, the lhs doesn't determine how the rhs is evaluated. The rhs is evaluated independently and then promoted to the type of the lhs."NULL v. zero
zero, and why C++'s insistance that 0 is better confuses me.
I don't see Linus saying that 0 can't be used as a NULL pointer. In factNULL v. zero
it clearly works since it is spread all over the kernel. It is just way
better to use NULL for pointers, 0 for ints, 0U for unsigned ints, '\0'
for chars, etc. so that it is clear what the constant is supposed to be.
This is a style issue and not a standards compliance issue.
null pointer but I just have to say that C++ is broken in that respect.)
Hey, C is broken the very same way ;) As you said, it is a style issue after all. It is the C++ people who are broken if you would like, not the language itself :)NULL v. zero
I disagree that the use of 0 for everything means that C++ is stongly typed.NULL v. zero
It seems backwards to me. A srongly typed language should be able to tell
the type of a constant without looking at the lhs of an assignment or the
parameter type in a function call. I don't believe you that C++ magically
knows which one... I think it evaluates it as an int, and then converts to
a pointer on assignment. Where this gets really ugly is with overloaded
functions and operators... if there is an int version and say, an int *
version, which one do you mean to call with a naked 0?
First of all, it is the fact that the language is strongly typed that allows it using 0 for anything, but not the opposite.NULL v. zero
But what do you mean by target type? LHS? If so, this goes againstNULL v. zero
fundamental aspects of the language. The evaluation of the RHS should
not be affected by the type of the LHS. The conversion should only
happen just before the assignment. And if such conversion happens, that
is an implicit conversion -- an implicit cast. That's not strong type
checking. And my point is that the RHS may be more than a simple
unadorned zero. When that is the case it would be nice to know ahead of
time if the type is an integer or a pointer so that it could warn about
improper manipulation of pointers (multiplying pointers for example).
Maybe that's a contrived corner case but I still see it as an ugly aspect
of the language.
Wait. I think I just understood what you mean:NULL v. zero
is only applied for a naked constant.
some cases. But I continue to think this is rather strange for a language
that claims to be strongly typed. (For example the removal of implicit
conversions of char constants was a good thing... I wish C could shed that
"feature" as well.)
No,NULL v. zero
Prior C standards haven't defined the value of NULL. The AS/400 maybe one of the more popular examples of a platform that didn't always use it.NULL v. zero
I agree that NULL is not used enough for assignments, but throw another hat into the ring with the syntaxNULL v. zero
if(!someptr) instead of if(someptr == NULL) or
if(!someboolean) instead of if(someboolean==0).
After all, the C language values were designed to allow for this, thus
simplifying the language expression. Anybody want to shoot me down?
!x vs x == 0
It's certainly allowed. If it is good style is another debate :)
NULL v. zero
I would like to bring this discussion back to a plain vanilla C one.NULL v. zero
It may help if you read K&R, 2nd Ed. page 102:
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.
But it is the reason why void-pointers were introduced in the first place.
> The execl example really *must* have "(const char *)NULL" (or NULL v. zero
> (const char *)0) to be theoretically correct in size.
vivi48: You are correct. I looked in my copy of the ISO/IEC FDIS 9899NULL v. zero
(i.e. the C-99 standard at ISO-level) and it says in para. 6.2.5 [26]:
requirements as a pointer to a character type. <footnote:> Meant to
imply interchangeability as arguments to functions, return values
from functions, and members of unions. <end quote>
type-mismatches between caller and callee, and it says that this
will have undefined behaviour, except if <quote:> one type is pointer
to void and the other is a pointer to a character type <end quote>.