According to §6.3.2.3 ¶3 of the C11 standard, a null pointer constant in C can be defined by an implementation as either the integer constant expression 0
or such an expression cast to void *
.
No, that a misleading paraphrase of the language spec. The actual language of the cited paragraph is
An integer constant expression with the value 0, or such an expression cast to type void *
, is called a null pointer constant. [...]
Implementations don't get to choose between those alternatives. Both are forms of a null pointer constant in the C language. They can be used interchangeably for the purpose.
Moreover, not only the specific integer constant expression 0
can serve in this role, but any integer constant expression with value 0 can do. For example, 1 + 2 + 3 + 4 - 10
is such an expression.
Additionally, do not confuse null pointer constants generally with the macro NULL
. The latter is defined by conforming implementations to expand to a null pointer constant, but that doesn't mean that the replacement text of NULL
is the only null pointer constant.
My implementation (GCC 9.4.0) defines NULL
in stddef.h
in the
following ways:
#define NULL ((void *)0)
#define NULL 0
Not both at the same time, of course.
Why are both of the above expressions considered semantically
equivalent in the context of NULL?
Again with the reversal. It's not "the context of NULL
". It's pointer context. There is nothing particularly special about the macro NULL
itself to distinguish contexts in which it appears from contexts where its replacement text appears directly.
And I guess you're asking for rationale for paragraph 6.3.2.3/3, as opposed to "because 6.3.2.3/3". There is no published rationale for C11. There is one for C99, which largely serves for C90 as well, but it does not address this issue.
It should be noted, however, that void
(and therefore void *
) was an invention of the committee that developed the original C language specification ("ANSI C" / C89 / C90). There was no possibility of an "integer constant expression cast to type void *
" before then.
More specifically, why do there
exist two ways of expressing the same concept rather than one?
Are there, really?
If we accept an integer constant expression with value 0 as a null pointer constant (a source-code entity), and we want to convert it to a runtime null pointer value, then which pointer type do we choose? Pointers to different object types do not necessarily have the same representation, so this actually matters. Type void *
seems the natural choice to me, and that's consistent with the fact that, alone of all pointer types, void *
can be converted to other object pointer types without a cast.
But then, in a context where 0
is being interpreted as a null pointer constant, casting it to void *
is a no-op, so (void *) 0
expresses exactly the same thing as 0
in such a context.
What's really going on here
At the time the ANSI committee was working, many existing C implementations accepted integer-to-pointer conversions without a cast, and although the meaning of most such conversions was implementation and / or context specific, there was wide acceptance that converting constant 0
to a pointer yielded a null pointer. That use was by far the most common one of converting an integer constant to a pointer. The committee wanted to impose stricter rules on type conversions, but it did not want to break all the existing code that used 0
as a constant representing a null pointer.
So they hacked the spec.
They invented a special kind of constant, the null pointer constant, and provided rules around it that made it compatible with existing use. A null pointer constant, regardless of lexical form, can be implicitly converted to any pointer type, yielding a null pointer (value) of that type. Otherwise, no implicit integer-to-pointer conversions are defined.
But the committee preferred that null pointer constants should actually have pointer type without conversion (which 0
does not, pointer context or no), so they provided for the "cast to type void *
" option as part of the definition of a null pointer constant. At the time, that was a forward-looking move, but the general consensus now appears to be that it was the right direction to aim.
And why do we still have the "integer constant expression with value 0"? Backwards compatibility. Consistency with conventional idioms such as {0}
as a universal initializer for objects of any type. Resistance to change. Perhaps other reasons as well.