Is ((void*)0) a null pointer constant?
Doesn't the bolded sentence contradict the author's claim that
((void*)0)
is not a null pointer constant?
No, it doesn't. (I confess to being a bit biased, since the referenced blog is mine.)
The bolded sentence says that its type and value are identical to those of the unparenthesized expression. That's not enough to imply that it's a null pointer constant.
Consider:
void *var = 0;
(void*)0
is a null pointer constant. ((void*)0)
has the same type and value as (void*)0
. var
also has the same type and value as (void*)0
, but var
clearly is not a null pointer constant.
Having said that, I'm 99+% sure that the intent is that ((void*)0)
is a null pointer constant, and more generally that any parenthesized null pointer constant is a null pointer constant. The authors of the standard merely neglected to say so. And since the description of parenthesized expressions in 6.5.1p5 specifically enumerates several other characteristics that are inherited by parenthesized expressions:
A parenthesized expression is a primary expression. Its type and value are identical to those of the unparenthesized expression. It is an lvalue, a function designator, or a void expression if the unparenthesized expression is, respectively, an lvalue, a function designator, or a void expression.
the omission is troubling (but only mildly so).
But let's assume, for the sake of argument, that ((void*)0)
is not a null pointer constant. What difference does it make?
(void*)0
is a null pointer constant, whose value is a null pointer of type void*
, so by the semantics of parenthesized expressions ((void*)0)
also has a value that is a null pointer of type void*
. Both (void*)0
and ((void*)0)
are address constants. (Well, I think they are.) So what contexts require a null pointer constant and do not accept an address constant? There are only a few.
6.5.9 Equality operators
An expression of function pointer type may be compared for equality to a null pointer constant. (An object pointer may be compared to an expression of type void*
, but a function pointer may not, unless it's a null pointer constant.) So this:
void func(void);
if (func == ((void*)0)) { /* ... */ }
would be a constraint violation.
6.5.16.1 Simple assignment
In an assignment, a null pointer constant may be assigned to an object of pointer-to-function type, and will be implicitly converted. An expression of type void*
that's not a null pointer constant may not be assigned to a function pointer. The same constraints apply to argument passing and initialization. So this:
void (*fp)(void) = ((void*)0);
would be a constraint violation if ((void*)0)
were not a null pointer constant. Thanks to commenter hvd for finding this.
7.19 Common definitions <stddef.h>
The macro NULL
expands to "an implementation-defined null pointer constant". If ((void*)0)
is not a null pointer constant, then this:
#define NULL ((void*)0)
would be invalid. This would be a restriction imposed on the implementation, not on programmers. Note that this:
#define NULL (void*)0
is definitely invalid, since macro definitions in standard headers must be fully protected by parentheses where necessary (7.1.2p5). Without the parentheses, the valid expression sizeof NULL
would be a syntax error, expanding to sizeof (void*)
followed by an extraneous constant 0
.
It is a parenthesized expression which contains a null pointer constant, so it indisputably is a null pointer value. Using it as an rvalue has exactly the same effect as using the "compliant" version as an r-value.
If there were some syntactic rules that could only accept a null pointer constant, it would not qualify. But I'm not aware of any (though I'm less expert on C).
And while neither one is a constant (referring to the formal grammar production), both can appear in a constant expression in an initializer, because both null pointer constants and address constants are allowed, and a constant null pointer value is explicitly included in the category of address constant.
Pointer comparisons also specifically mention null pointer constants... but here pointer values are also accepted, and all null pointer values are treated equally. Same for the ternary and assignment operators.
Please do be aware that these rules are quite different in C++, where both the above expressions are constant null pointer values of type void*
, but not universal null pointer constants. Null pointer constants in C++ are integral constant expressions which evaluate to zero. And void*
doesn't implicitly convert to other pointer types.