|
|
Subscribe / Log in / New account

NULL v. zero

Back in June, this page looked at the sparse utility, which is being used to search out various kinds of errors in the kernel code base. Recently, large numbers of patches have gone in to address one particular sparse complaint: using an integer 0 to represent a null pointer value. These patches (example) have struck some developers as useless code churn, leading to complaints like:

If you want people to conform people to a certain CodingStyle please document officially in the kernel, sparse isn't distributed with the kernel and the sparse police is silently changing the kernel all over the place with sometimes questionable benefit. Only the __user warnings had really found the bugs, but the rest I've seen changes perfectly legal code.

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.

In other words:

	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
KernelCoding style
KernelNULL and zero


to post comments

NULL v. zero

Posted Jul 15, 2004 11:38 UTC (Thu) by ikm (guest, #493) [Link] (26 responses)

The ISO/IEC 9899:1999 standard states that

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 obvious therefore that NULL is always #defined as 0 or ((void *)0).

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.

NULL v. zero

Posted Jul 15, 2004 11:46 UTC (Thu) by ikm (guest, #493) [Link]

[In the last paragraph: to null, not to zero, of course.]

NULL v. zero

Posted Jul 15, 2004 12:15 UTC (Thu) by dougm (guest, #4615) [Link] (6 responses)

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

Posted Jul 15, 2004 15:52 UTC (Thu) by ikm (guest, #493) [Link] (5 responses)

Thanks for the correction. Style coding standards may be more strict than the language itself, of course.

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.

NULL v. zero

Posted Jul 15, 2004 17:03 UTC (Thu) by kamil (guest, #3802) [Link] (4 responses)

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:

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 :-).

NULL v. zero

Posted Jul 15, 2004 19:25 UTC (Thu) by knobunc (guest, #4678) [Link] (1 responses)

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.

-ben

NULL v. zero

Posted Jul 15, 2004 19:34 UTC (Thu) by Ross (guest, #4065) [Link]

I think the other person mean (const char *)NULL, but maybe not.

NULL v. zero

Posted Jul 15, 2004 19:32 UTC (Thu) by Ross (guest, #4065) [Link]

Yeah, but unfortunately you can't pass plain NULL either (though it works
on almost any system). You have to cast it to the correct pointer type.
(Of course using 0 is even worse.)

NULL v. zero

Posted Jul 15, 2004 22:45 UTC (Thu) by ikm (guest, #493) [Link]

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 :)

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!

NULL v. zero

Posted Jul 15, 2004 13:34 UTC (Thu) by elanthis (guest, #6227) [Link] (9 responses)

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.

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.

NULL v. zero

Posted Jul 15, 2004 15:41 UTC (Thu) by ikm (guest, #493) [Link] (8 responses)

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).

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.

NULL v. zero

Posted Jul 15, 2004 18:23 UTC (Thu) by dvdeug (guest, #10998) [Link] (1 responses)

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

Posted Jul 15, 2004 23:03 UTC (Thu) by ikm (guest, #493) [Link]

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

Posted Jul 15, 2004 19:37 UTC (Thu) by Ross (guest, #4065) [Link] (2 responses)

Actually, no. C++ is a little more strict. C++ doesn't have a generic
pointer type like C.

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
type should be, and it masks the bug of not including stdlib.h before
calling malloc.

So in C++ the nil pointer is 0 (they are very adamant about this).
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.

NULL v. zero

Posted Jul 15, 2004 20:20 UTC (Thu) by dvdeug (guest, #10998) [Link] (1 responses)

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

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.

NULL v. zero

Posted Jul 15, 2004 19:40 UTC (Thu) by Ross (guest, #4065) [Link] (2 responses)

Is it not strange for the language to use the lhs to determine how to
evaluate the rhs? This always bothered me about C++. For example:

int bob;
void *nancy;

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

NULL v. zero

Posted Jul 15, 2004 22:02 UTC (Thu) by sir99 (guest, #3286) [Link] (1 responses)

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.

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

Posted Jul 16, 2004 19:35 UTC (Fri) by Ross (guest, #4065) [Link]

"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."

That was my point, really. That's why NULL as (void *)0 is better than just
zero, and why C++'s insistance that 0 is better confuses me.

NULL v. zero

Posted Jul 15, 2004 19:43 UTC (Thu) by Ross (guest, #4065) [Link] (6 responses)

I don't see Linus saying that 0 can't be used as a NULL pointer. In fact
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.

(C++ people would disagree and say that 0 is the correct value for the
null pointer but I just have to say that C++ is broken in that respect.)

NULL v. zero

Posted Jul 15, 2004 22:33 UTC (Thu) by ikm (guest, #493) [Link] (5 responses)

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 :)

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!)

NULL v. zero

Posted Jul 16, 2004 19:40 UTC (Fri) by Ross (guest, #4065) [Link] (4 responses)

I disagree that the use of 0 for everything means that C++ is stongly typed.
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?

NULL v. zero

Posted Jul 16, 2004 20:15 UTC (Fri) by ikm (guest, #493) [Link] (3 responses)

First of all, it is the fact that the language is strongly typed that allows it using 0 for anything, but not the opposite.

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.

NULL v. zero

Posted Jul 17, 2004 4:32 UTC (Sat) by Ross (guest, #4065) [Link]

But what do you mean by target type? LHS? If so, this goes against
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.

NULL v. zero

Posted Jul 17, 2004 4:41 UTC (Sat) by Ross (guest, #4065) [Link] (1 responses)

Wait. I think I just understood what you mean:

char *bob=0*0;

would not compile as 0*0 is an integer expression. The implicit conversion
is only applied for a naked constant.

Then I retract my statement about the compiler not being able to warn in
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.)

NULL v. zero

Posted Jul 17, 2004 11:08 UTC (Sat) by ikm (guest, #493) [Link]

No,

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.

NULL v. zero

Posted Jul 23, 2004 15:44 UTC (Fri) by Nelson (subscriber, #21712) [Link]

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.

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

NULL v. zero

Posted Jul 16, 2004 22:59 UTC (Fri) by jabcslwn (guest, #11815) [Link] (2 responses)

I agree that NULL is not used enough for assignments, but throw another hat into the ring with the syntax
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

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.

NULL v. zero

Posted Jul 17, 2004 4:33 UTC (Sat) by Ross (guest, #4065) [Link]

It's certainly allowed. If it is good style is another debate :)

NULL v. zero

Posted Jul 22, 2004 18:59 UTC (Thu) by h.j.thomassen (guest, #15232) [Link] (1 responses)

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

NULL v. zero

Posted Jul 23, 2004 14:36 UTC (Fri) by vivi48 (guest, #6412) [Link]

> The execl example really *must* have "(const char *)NULL" (or
> (const char *)0) to be theoretically correct in size.

or (void *)NULL or (void *)0 because the spec explicitely says that with va_arg you can read back a void * as a char *.

NULL v. zero

Posted Jul 26, 2004 16:41 UTC (Mon) by h.j.thomassen (guest, #15232) [Link]

vivi48: You are correct. I looked in my copy of the ISO/IEC FDIS 9899
(i.e. the C-99 standard at ISO-level) and it says in para. 6.2.5 [26]:

<quote:> A pointer to void shall have the same representation and alignment
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>

(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
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>.

Thanks for pointing :-) this out.


Copyright © 2004, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds