nullptr
2023-01-08
org: | ISO/IEC JCT1/SC22/WG14 | document: | N3077 | |
target: | IS 9899:2023 | version: | 1 | |
date: | 2023-01-08 | license: | CC BY |
This makes
nullptr
a null pointer constant, as well as0
, and(void*)0
, but it seems to leave out(void*)nullptr
which seems like a minor oversight.
Add
(void*)nullptr
to the list of allowed forms of a null pointer constant.
No this was not an oversight, but a choice. Already in C17 (void*)NULL
is not always a null pointer constant. If NULL
is (void*)0
this expands to
(void*)((void*)0)
which not a null pointer constant. The property of being so is bound to a specific syntactic derivation, namely a void*
cast of an integer constant expression of value 0
.
For example we have
unsigned* a = true ? (signed*)0 : (void*)0; // invalid, initializer expression is (signed*)0
unsigned* b = true ? (signed*)0 : (void*)1; // valid, initializer expression is (void*)0
unsigned* c = true ? (signed*)0 : (void*)((void*)0); // valid, initializer expression is (void*)0
As well gcc as clang diagnose the line for a
but not for b
or c
.
The idea with the proposed wording for C23 is to make C’s type system stricter with this new constant nullptr
and its corresponding type. We wanted that any modification of the type of nullptr
(by cast) would strip away the property of being a null pointer constant.
Although we would not be strongly opposed to make a change, here, we prefer it as it is and do not propose any action.
These comments all have in common that they suggest to introduce a special conversion rule to nullptr_t
from null pointer constants.
6.3.2.4
nullptr_t
1 The type
nullptr_t
may be converted tovoid
,bool
, or to a pointer type. The; the result is avoid
expression,false
, or a null pointer value, respectively.
2
TheA null pointer constant or a value of typenullptr_t
may be converted toitselfnullptr_t
.
For completeness, they would also require an addition to the rule for explicit conversions.
6.5.4 Cast operators
…
4 A pointer type shall not be converted to any floating type. A floating type shall not be converted to any pointer type. The type
nullptr_t
shall not be converted to any type other thanvoid
,bool
or a pointer type.No type other than nullptr_t shall be converted to nullptr_t.If the target type isnullptr_t
, the cast expression shall be a null pointer constant or have typenullptr_t
.
Introduces incompatible semantics with C++ regarding what can be converted to
nullptr_t
type in the following example (rejected in C, accepted in C++):
void func(nullptr_t);
(0); func
This example should be accepted, as in C++.
Introduces a different kind of incompatible semantics with C++ regarding the following example (rejected in C, accepted in C++):
nullptr_t val;
= 0; val
These examples should be accepted, as in C++.
Both comments depend actually on the status of simple assignment. A possible resolution would be
6.5.16.1 Simple assignment
Constraints
1 One of the following shall hold125):
…
— the left operand has an atomic, qualified, or unqualified version of the
nullptr_t
type andthe type ofthe right operand is a null pointer constant or its type isnullptr_t
126);
— the left operand is an atomic, qualified, or unqualified pointer, and the type of the right operand isnullptr_t
;
— the left operand is an atomic, qualified, or unqualified bool, and the type of the right operand is
nullptr_t
;
— the left operand is an atomic, qualified, or unqualified pointer, and
the type ofthe right operand is a null pointer constant or its type isnullptr_t
; or
— the left operand has type atomic, qualified, or unqualified bool, and the right operand is a pointer or its type is
nullptr_t
.
Introduces incompatible semantics with C++ regarding behavior of the following examples (rejected in C, accepted in C++):
nullptr_t val;
(void)(1 ? val : 0);
(void)(1 ? nullptr : 0);
This example should be accepted, as in C++.
Since C and C++ largely disagree on the semantics of the conditional operator (lvalue/rvalue status and result type) the possible use of that operator in common code for both languages is very much restricted, anyhow, and cannot be recommended.
If WG14 still wishes to adapt the property in question it would go as follows.
6.5.14 Conditional operator
Constraints
…
3 One of the following shall hold for the second and third operands122):
…
—
both operands haveone operand hasnullptr_t
type and the other is a null pointer constant or hasnullptr_t
type;
…
Semantics
…
7 If both the second and third operands are pointers, the result type is a pointer to a type qualified with all the type qualifiers of the types referenced by both operands; if one is a null pointer constant (other than a pointer) or has type
nullptr_t
and the other is a pointer, the result type is the pointer type;if both the second and third operands haveone operand hasnullptr_t
type, the result also has that typenullptr_t
type and the other is a null pointer constant or hasnullptr_t
type, the result hasnullptr_t
type. Furthermore, if both operands are pointers to compatible types or to differently qualified versions of compatible types, the result type is a pointer to an appropriately qualified version of the composite type; if one operand is a null pointer constant, the result has the type of the other operand; otherwise, one operand is a pointer to void or a qualified version of void, in which case the result type is a pointer to an appropriately qualified version ofvoid
.
Does WG14 want to resolve NB comments US 10 and US 24 as indicated above?
Does WG14 want to resolve NB comments US 22 as indicated above?
The
nullptr
predefined keyword andnullptr_t
type was added, representing a separate way to access a null pointer constant. This may be redundant and unnecessary and the problems it addresses not suitably sufficient to justify keeping it. Users voiced concern over keeping it in the C Standard, and a few audited existing codebases and existing implementations to see if there was any need beyond just settling on an existing null pointer constant such as(void*)0
. Additional users found existing practice where0
and0L
were being used as the null pointer constant (e.g., in definitions ofNULL
) for embedded chips like those employed by U.S. vendor [REDACTED]. While some vendors responded positively to being encouraged to change from using0
and0L
to(void*)0
, others either did not respond or rejected outright the idea that they would change theirNULL
macro from0
to(void*)0
. Some platforms use a special__null
even for their non-C++ platforms. Other vendors, such as [REDACTED], used0
/0L
explicitly unless a macro turned on to opt into a newer(void*)0
definition of the macro. It is noted these users and implementations were in the vast minority, even though they do definitively exist. Given this additional information, poll the C Standards Committee again if thenullptr
changes from N3042 should remain or be removed completely from the C Standard.
Other than repeating the confusing state of affairs of null pointer constants in the field, this NB comment does not seem to add new information to the usefulness (or not) of the nullptr
feature itself.
In the contrary, the given arguments make it clear that we owe our users a new portable tool that allows them to circumvent all the ambiguities of the term “null pointer constant” and to overcome the lack of cooperation of some implementations to improve the situation.
All of this has been discussed at length during the year-long process in WG14. In particular, an initial proposal that would have gone merely in a direction of replacing the use of “integer constant expressions of value zero” by (void*)0
has not been seen favorable by WG14, which explicitly wanted a version that is better suitable for cross language use in C and C++ and that explicitly expressed a need for the type nullptr_t
as it exists in C++.
Since there is no new information, here, we think that the found consensus should not be questioned.
This footnote can’t be implemented because it would require the implementation to inspect the value of an object as an assignment constraint at compile time. Consider:
nullptr_t lhs_val;
nullptr_t rhs_val = (nullptr_t)(void *)1; // UB
= rhs_val; // Expects a constraint violation here per the footnote but nothing normative lhs_val
I think the footnote should read that it’s undefined behavior rather than a constraint violation.
This refers to the footnote
126) The assignment of an object of type
nullptr_t
with a value of another type, even if the value is a null pointer constant, is a constraint violation.
First, the assessment that the definition of rhs_val
is UB is wrong. In fact, the initializer performs a cast from a pointer value to nullptr_t
. According to the constraints in 6.5.4 p4 for casts
No type other than nullptr_t shall be converted to nullptr_t.
So this is a constraint violation, already, and would remain so even with the modifications to conversions discussed above.
Also nothing in that footnote suggests that it would be a constraint violation to do the assignment to lhs_val
, since rhs_val
has the same type as lhs_val
, namely nullptr_t
.
(Also, the example in the comment suggest the UB to have already happened during initialization or assignment to rhs_val
. Discussing any further behavior of the code after UB is not very productive.)
So we think that no action is needed for this NB comment.
Introduces incompatible semantics with C++ regarding the behavior of the following examples (accepted in C, rejected in C++):
nullptr_t val;
bool b1 = val;
bool b2 = nullptr;
These examples should be rejected, as in C++.
Indeed these examples are accepted in C23 as proposed. This has been a diligent choice.
C traditionally allows much more implicit conversion than C++: all scalar values in C17 implicitly convert to bool
. Our users can expect that this stays this way, in particular because in C a common use pattern is to have a value (being a pointer value or a null pointer) hidden inside a macro. It would be very annoying for C programmers if the status of code like
bool b3 = BASE;
would depend on BASE
expanding to
nullptr_t
.Having a behavior for nullptr
that would be different from all other scalars in such a context of implicit conversion to bool
would be an impediment for an adoption of the feature in parts of the C community. If it is defined as a scalar, it should act as a scalar.
It seems that this property has been discussed during the processing for this feature, so we don’t think that the consensus should be questioned, here.
Introduces incompatible semantics with C++ regarding the following example (rejected in C, accepted in C++):
(nullptr_t)nullptr;
because
nullptr_t
is notvoid
,bool
, or a pointer type (so it cannot be cast to itself). Note, this might be editorial because the last sentence of the para says “No type other than nullptr_t shall be converted to nullptr_t” but it’s unclear because of the use of “only” in the preceding sentence.
Change the penultimate sentence to:
The type
nullptr_t
shall not be converted to any type other thanvoid
,bool
,nullptr_t
, or a pointer type.
While we would not be opposed to such a change we don’t think that it is necessary. In particular, the clause for the cast operators already seems to assume that when the target type is the same as the source type, that no conversion is actually performed.
For a different resolution see the proposed text for “Cast Operators”, above.
These two comments address the same problem, namely equality operators.
Almost all places that allow operands of type
nullptr_t
do so independent of whether the particular operand is a null pointer constant or another expression of that type. However, the rules for equality operators only allow comparison of a pointer that is not a null pointer constant with anullptr_t
value that is a null pointer constant, not with any othernullptr_t
value. Since it seems C++ implementations allow such comparisons, disallowing them in C might not be intentional.
If it’s not desired to disallow such comparisons after all, in the last bullet point in paragraph 2, change “is a null pointer constant” to “is a null pointer constant or has type
nullptr_t
”. In paragraph 6, change “the other is a null pointer constant, the null pointer constant is converted” to “the other is a null pointer constant or has type nullptr_t, the null pointer constant or operand of typenullptr_t
is converted”.
There is a missing case for the comparison of pointers with type
nullptr_t
see accompanying document
We follow the resolution proposed by AFNOR:
6.5.9 Equality operators
Constraints
…
– one operand is a pointer and the other is a null pointer constant or has type
nullptr_t
.
…
Semantics
…
6 Otherwise, at least one operand is a pointer. If one operand is a pointer and the other is a null pointer constant or has type
nullptr_t
, they compare equal if the former is a null pointer, the null pointer constant is converted to the type of the pointer. If one operand is a pointer to an object type and the other is a pointer to a qualified or unqualified version of void, the former is converted to the type of the latter.