LWN.net Logo

NULL v. zero

NULL v. zero

Posted Jul 15, 2004 11:38 UTC (Thu) by ikm (subscriber, #493)
Parent article: NULL v. zero

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.


(Log in to post comments)

NULL v. zero

Posted Jul 15, 2004 11:46 UTC (Thu) by ikm (subscriber, #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 (subscriber, #4615) [Link]

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 (subscriber, #493) [Link]

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 (subscriber, #3802) [Link]

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 (subscriber, #4678) [Link]

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 (subscriber, #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 (subscriber, #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 (subscriber, #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 (subscriber, #6227) [Link]

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 (subscriber, #493) [Link]

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 (subscriber, #10998) [Link]

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 (subscriber, #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 (subscriber, #4065) [Link]

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 (subscriber, #10998) [Link]

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 (subscriber, #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 (subscriber, #4065) [Link]

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]

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 (subscriber, #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 (subscriber, #4065) [Link]

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 (subscriber, #493) [Link]

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 (subscriber, #4065) [Link]

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 (subscriber, #493) [Link]

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 (subscriber, #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 (subscriber, #4065) [Link]

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 (subscriber, #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

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