Jason Curl said:
The FAQ is useful, Q7.13 directly says what I'm doing is wrong.
I understand that 0 in a ptr context is NULL and that ANSI also allows
(void *)0 as an interpretation of NULL. I also understand that NULL for
an (int *) can be internally different to (char *) (Q5.17).
No, no, this is wrong. You must stop thinking of NULL as _being_
something. NULL is merely a macro; it has no real existence except as a
convenient identifier for something real. And in the paragraph above,
you confuse it with two, very different, real things.
On the one hand, you have a null pointer. This is an actual pointer
value, which exists during run time. It is a valid pointer, in that it
can be passed to functions and assigned to pointer objects, but it does
not point at any object; dereferencing it causes undefined behaviour. It
is this null pointer which can have different representations depending
on type.
Then there's a null pointer _constant_. It exists in your source code.
It can be either an integer constant with value 0, or such a constant
cast to void *. Of course, the most common null pointer constants you
find in real source code are 0 and (void *)0, but notoriously, '-'-'-'
would work just as well. Any null pointer constant serves just as well
for any type null pointer; int *ip=0; is as valid as float *fp=0;. You
use a null pointer constant in your source code whenever you want a null
pointer at run time, just as you use, say, '\n' whenever you want a
newline at run time.
And finally, there's NULL. NULL is a macro, defined in several Standard
headers; it is defined as a null pointer constant. Note: _a_ null
pointer constant. The Standard doesn't require it to be any kind of null
pointer constant, and you needn't care; all you need to know is that
NULL is some kind of null pointer constant, and can be used for clarity
wherever you would use a normal npc. But you don't _have_ to; you can
program in C without ever using NULL, if you prefer to use an
undisguised null pointer constant.
What I don't understand is why typecasting a NULL is not necessary.
For example:
a) int *p = NULL;
b) execl("/bin/sh", "sh", "-c", "date", NULL);
So (a) is OK, as the compiler can determine that NULL (if 0) should be
(int *)0.
It can determine the same thing, btw, if NULL is defined as (void *)0,
or even as ((void *)!sizeof(int)), but yes.
But for (b) I get the impression that it is incorrect and
should really be:
execl("/bin/sh", "sh", "-c", "date", (char *)NULL);
Yes, but that's because execl() is a special case. It is a variadic
function. You need to be much more careful with your parameters to
variadic functions than with normal functions. Since the compiler has
exactly zero information about the variadic arguments, it cannot know
that you meant a null pointer constant rather than an integer zero with
that 0. Even if you pass a (void *)0, it cannot figure out whether the
function needs an int * or a float *. Therefore, it needs to be told,
explicitly.
Three asides, here: 1. This is also true for the *printf()/*scanf()
family of functions, since they're variadic, as well; and obviously also
for any variadic functions you might define yourself. 2. Null pointer
constants are not the only thing affected; you must also be careful with
chars, shorts, and floats, since the default argument promotions are
performed on the variadic arguments (but not on the fixed ones!). 3. All
of this is true of functions with an old-style, non-prototype
declaration, or without a declaration at all, but ideally you shouldn't
have any of those around any more.
Summarising:
If you are not dealing with pointers, don't use NULL at all.
If you are dealing with the variable arguments of a variadic function,
and need to pass a literal null pointer, use either NULL cast to the
required type, or 0 cast to the required type. (You could, in principle,
also use (void *)0 cast to the required type, but that's overkill.)
Either works just as well as the other.
If you need a literal null pointer in any other pointer context, use
either 0, or (void *)0, or NULL, as you please. The compiler will detect
that you used a null pointer constant in a pointer context, and convert
it to the required type null pointer.
Richard