Hi Christopher,
Sorry, my organization uses Outlook, and Outlook won’t let me reply inline. I configured it to use plain text and quote messages using ‘> ‘ but it appears to have ignored me (at least for this reply). By using the website I hoped that my messages would be in the most commonly-acceptable format and be accessible to everyone. It also allows me to reply from anywhere without logging into my email account.
We definitely want to redefine NULL if we’re adding this feature.
Otherwise, what is the purpose of this feature?
For allowing programmers to annotate pointers that can be NULL.
The existing definition of NULL doesn’t incorporate a pointer target
type qualifier, therefore it can be converted silently to a pointer to const,
or a pointer to volatile. I expect that it would also be converted silently
to a pointer to optional.
I don’t think you are considering the common case where a pointer from a
function that can return null is assigned. Nobody I know writes code like this
(thankfully, as this has undefined behaviour when out of memory):
char *s = strcpy(malloc(strlen(t)+1), strlen(t));
Most people actually write code more like this:
char optional *const r = malloc(strlen(t)+1);
if (r == NULL) return Error_OutOfMemory;
char *s = strcpy(r, strlen(t));
Since the return value of malloc is assigned before being used, it can be
assigned to a suitably-qualified pointer.
Checking that a pointer doesn’t contain a null pointer, but NULL is not
considered a null pointer? NULL definitely needs to be defined as:#define NULL ((void _Optional *)0)
Sorry but what you are asking is impossible without breaking compatibility
with all existing C code in the world. No standards committee for any language
would accept that.
For the same reason, fopen, strstr, calloc and malloc must never be
changed to return a pointer to an optional-qualified object.
Defining yet another null pointer name, as C++ did with nullptr, is just
confusing, and provides no benefit.
I think you’ve already articulated what benefit it provides
It allows the
compiler to check that literal null values aren’t assigned to arguments or
variables which haven’t been explicitly typed as allowing null.
It would be very easy to search a codebase for all instances of NULL
but we need to provide a plausible migration path, not break the world.
The ‘optional’ qualifier is a tool to let people who want to write safer code, like ‘const’.
It doesn’t force anyone to do that, and I’m sure there will be hold-outs just like people
who don’t use ‘const’ qualifiers because they are too infectious.
The compiler is neither required to refuse the following:
void modify(const int *p) {*p = 42;}
It’s not required to generate working object code either.
From the C17 standard draft, Undefined Behaviour:
— An attempt is made to modify an object defined with a const-qualified type through use of an
lvalue with non-const-qualified type (6.7.3).
In contrast, accessing an object with an optional-qualified type cannot be made undefined behaviour unless all such objects first undergo type conversion to the equivalent unqualified type (either automatically, through inference in the compiler, or explicitly, by a type cast).
My preference was for accessing an object with an optional-qualified type to behave exactly the same as accessing an object without this qualifier, because:
- It makes the new feature simple to implement in legacy compilers, including those for obscure systems. (They simply ignore the new qualifier for all purposes except type conversions/validation, for which its behaviour is exactly like existing pointer target type qualifiers.)
- It removes the need for an explicit cast to remove the ‘optional’ qualifier in situations where the pointer doesn’t have to cross another interface boundary.
- I don’t want to introduce another source of undefined behaviour.
No. The compiler should convert the (int optional *) to (int *) without
a warning, since it sees a preceeding NULL check in scope where the
automatic conversion is being done. A cast should not be needed.
This would also be my ideal, but have you considered the practicality of requiring this for all of the C compilers in the world, including when compiling with optimisation disabled? One of the big advantages of C is that it is relatively simple to create a compiler for any platform, and such compilers can be fast and memory-efficient. Not all compilers are GCC or Clang. A lot of them don’t have the ability to do the kind of advanced logical inference that you (and I) might want.
Here’s an example of a compiler from the 1980s (the original ARM compiler, actually) which is still being actively maintained and recently gained support for C18: RISC OS Open: Desktop Development Environment
No. No casts. Not even C+±like casts.
I don’t like casts either. Writing code without casts is one of my main measures of quality.
Having said that, all of my colleagues (most of whom have admittedly already migrated to C++) think that the ability to do something like const_cast in C would be a big improvement. I find the syntax of C++ casts horribly ugly (reputedly deliberately so), but I can’t argue with their logic that it is better not to have to discard all type information in the rare occasions where casting is unavoidable.
That completely defeats the purpose of the feature.
Would you say the fact that it’s sometimes necessary to cast away the ‘const’ qualifier completely defeats the purpose of that qualifier? For example, it’s impossible to implement the standard C library’s strstr function without casting away a const qualifier.
(I’ve not had the chance to consider the proposal carefully yet, but I do have some driveby comments.)
Please note that nullptr was adopted into C23 from this proposal: Introduce the nullptr constant
I think there might be a misunderstanding about what you’ve quoted. It is UB to modify an object defined as const, not declared: Compiler Explorer
+1, a proposal requiring the implementation to perform a significant analysis is unlikely to be successful in WG14.
Hi,
Alejandro_Colomar: Defining yet another null pointer name, as C++ did with nullptr, is just confusing, and provides no benefit.Please note that nullptr was adopted into C23 from this proposal:
Introduce the nullptr constant
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3042.htm
Has this been formally accepted? For C2x?
The latest draft that I know is N2912. Is there any more recent one?
About that proposal, I’d like to strongly oppose to it.
First of all, let me remind about the relevant XKCD:
https://xkcd.com/927/
First of all, let’s examine what the relevant standards (ISO C, and
POSIX) have to say about NULL:
-
ISO C (N2912):
7.20 Common definitions <stddef.h> [...]3 The macros are
NULL
which expands to an implementation-defined null pointer constant;[...] -
POSIX:
NULL
Null pointer constant.
[CX] The macro shall expand to an integer constant expression
with the value 0 cast to type void *.
CX in POSIX means that it’s an extension to ISO C.
So we have that, in ISO C, NULL is a black box, and should be treated as
such by programmers. That an implementation choses to define it as 0 is
absolutely out of the business of any programmer. Still, that
implementation is a very low quality one, IMO. POSIX, mandates
that NULL be a much safer thing: (void *)0 (and implementations usually
wrap that in parentheses too, but that’s irrelevant for this discussion).
Now, the behavior of nullptr, as I understand it, is that it’s 100%
compatible with POSIX’s NULL: it offers the same automatic conversions
(there’s a strong differentiation between pointers and integers). So, I
don’t see a reason to reinvent the wheel here. That some irresponsible
compiler authors decide to implement the null pointer macro NULL to
expand to an integer expression shouldn’t be reason enough to force good
implementations to adapt to them.
I think it should be enough that ISO C tightened the requirement to
align with the POSIX definition, by requiring that NULL expands to an
expression of type ‘void *’. Since the current definition of ISO C is
just a black box, it is perfectly valid to clarify that black box.
I also think that ISO C would do a better job by also deprecating the
constant expression 0 to mean a null pointer. We should unify the null
pointer into a single entity, rather than splitting it with yet another
way to define it. Let’s just make a strong differentiation between
pointers and integers.
If ISO C fears that there might still be systems where address 0 is a
usable one, it could specify NULL as the following:
NULL The null pointer constant.
The macro shall expand to an expression of type void *.
See also a current patch discussion in the Linux man-pages mailing list
about adding a new page for documenting NULL:
https://lore.kernel.org/linux-man/20220726124800.108850-1-alx.manpages@gmail.com/T/#u
Cheers,
Alex
Hi,
If ISO C fears that there might still be systems where address 0 is a
usable one, it could specify NULL as the following:NULL The null pointer constant.
The macro shall expand to an expression of type void *.
maybe s/expression/implementation-defined expression/
Also, IMO, C++ didn’t need to add nullptr either. They had a slightly
better reason to add it, because they couldn’t use ‘void *’, as C++
doesn’t allow converting that type to another pointer type. But in C we
don’t have that problem. Still, I think it would have been fine for C++
to only add nullptr_t (but nor nullptr), and define NULL as (nullptr_t)0.
Cheers,
Alex
If ISO C fears that there might still be systems where address 0 is a
usable one, it could specify NULL as the following:
This is a bad misunderstanding of what the ‘0’ hiding behind the macro NULL’s definition actually is. It certainly is not a machine address. See Question 5.5 and Question 5.13
Hi Christopher,
[Christopher_Bazley] Christopher_Bazley
https://discourse.llvm.org/u/christopher_bazley
August 6If ISO C fears that there might still be systems where address 0 is a usable one, it could specify NULL as the following:This is a bad misunderstanding of what the ‘0’ hiding behind the macro
NULL’s definition actually is. It certainly is not a machine address.
See Question 5.5 https://c-faq.com/null/machnon0.html and Question
5.13 https://c-faq.com/null/varieties.html
I’m not confusing all-0s bit pattern with (void *)0. I know the
difference. I’m just saying that it would be good to avoid that wording
in ISO C exactly to avoid that confusion.
However, AFAIK, POSIX is considering a change by which the all-0s bit
pattern would represent the null pointer. This is a good move for POSIX
systems, because de facto that is already true. All POSIX systems
interpret memset(&ptr, 0, sizeof(ptr)) de-facto as setting that pointer
to null, even if POSIX doesn’t mandate that.
For ISO C, I see three alternatives (in order of my preference):
-
Embrace the 0s bit pattern, and mandate that a bit pattern of 0s shall
be interpreted as a null pointer. This might not be possible if some
unicorn systems still use 0 as a usable address. I ignore if those
exist nowadays. C2x already embraced the 2’s complement arithmetics,
and this change would be similar (making obsolete those unicorn
architectures). -
Avoiding any mentions to 0, to avoid confusion with the all-0s bit
pattern. This is what I’d prefer, in the case that ISO C can’t go so
far as to mandate that all-0s represents a null pointer. -
Use the same exact wording as POSIX.1-2008, which says that (void *)0
is a null pointer, but all-0s bit pattern may or may not represent the
null pointer. This is not good at all because it add confusion for no
good reason. Anyway, why should a programmer know the exact details of
the definition of the NULL macro?
Cheers,
Alex
The nullptr_t type and the nullptr constant, as proposed in N3042, were accepted for C23 during the WG14 meeting held in July, 2022. The changes have been incorporated in the latest working draft, N3047.
I recommend reading N3042, and perhaps the previous papers that introduced the feature to C++, before concluding that the feature should be removed.
This is not the right forum to voice opposition to a WG14 or WG21 proposal.
Since N3042 has already been accepted and WG14 is no longer accepting new papers for C23, the only way to object to the inclusion of the proposal in C23 is to file a NB (National Body) comment against the CD (Committee Draft) during the ballot period.
#define optional_cast(X) _Generic((*X), \ double :optional_cast_double, \ float: optional_cast_float, \ char: optional_cast_char, \ int: optional_cast_int \ )(X)However that won’t work for user-defined types. A built-in compiler function to do the casting would solve that issue, but that is straying into C++ territory.
I tried to improve the above macro definition to handle any of the infinite number of user-defined types by using _Generic() with typeof():
#define optional_cast(X) _Generic(*(X), \
typeof(*(X)) : (typeof(*(X)) *)(X) \
)
It doesn’t work though, for reasons explained by Clang:
due to lvalue conversion of the controlling expression, association of type 'typeof (*(i))' (aka 'const double') will never be selected because it is qualified [-Wunreachable-code-generic-assoc]
I don’t know how to force lvalue conversion of the type returned by typeof(), therefore the controlling expression doesn’t match a pointer to any qualified type; even if it did, the associated expression wouldn’t remove the pointer target type qualifier as I intended.
If _Generic() cannot be used to implement a type-safe way of removing type qualifiers (like const_cast does) then that seems like a missed opportunity.
Yes, it was formally adopted at our last meeting. The latest draft (basically the last one before we ballot) is https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3047.pdf.
Sorry to hear that; it passed the committee 17 in favor, one opposed, three abstentions (so quite strong support).
It’s incorrect to say that nullptr and NULL (POSIX or otherwise) are 100% compatible, but they are certainly close. nullptr is strongly typed, NULL is weakly typed (it relies on pointer conversions). So you can spot differences with code like:
int i = NULL; // "Fine", even with POSIX NULL
int j = nullptr; // Error
FWIW, I was one of the people in favor of this proposal in WG14. nullptr adds some extra type safety benefits over NULL.
I cannot imagine a world in which that’s plausible. It took us 30+ years to remove K&R C functions, I would imagine deprecating 0 as a null pointer constant would take at least that long. Also, changing C’s definition of NULL to require the cast to void * will break code that’s been valid for a long, long time which is why the committee was happier to consider adopting the same feature that C++ already has. It gave us a clean path forward for improving how to represent a null pointer in C and it did so in a way that helps people who have shared C and C++ code bases.
To clarify a little bit – voicing opposition (or support) here has no direct impact on decisions already made by either committee. That said, it is perfectly fine to voice opposition or support for things which the committee is still deliberating and you’d like to give your perspective on as a user.
You might be interested in the N2927 (Not-so-magic - typeof for C) (and related N2930 (Consider renaming remove_quals)) proposal adopted for C23. The end result is that C23 includes a typeof_unqual type specifer.
I think there might be a misunderstanding about what you’ve quoted. It is UB to modify an object defined as
const, not declared: Compiler Explorer
Thanks for pointing out my mistake. The example code helped illustrate too. I’m not in the habit of writing code like that, so my brain conflated ‘defined’ and ‘declared’. ![]()
A lot of them don’t have the ability to do the kind of advanced logical inference that you (and I) might want.
+1, a proposal requiring the implementation to perform a significant analysis is unlikely to be successful in WG14.
I’d love to hear your views on my proposal. The requirement for casting away a pointer target type qualifier before passing a pointer to another function is a pity. Maybe you like the address-of solution, or can think of a different way to alleviate it. My colleagues seemed surprisingly happy with a const_cast-like solution, considering the scrutiny they subjected my idea to.
What I’m really looking for is a simple extension to the type system that provides value to users of systems where the compiler and runtime environment mightn’t catch every error, adheres to existing syntax and semantics, but is also powerful enough to satisfy the perceived requirement for null/not-null annotations in major compilers like Clang.
Hi Tom and Aaron,
[tahonermann] tahonermann https://discourse.llvm.org/u/tahonermann
August 6Alejandro_Colomar: Has this been formally accepted? For C2x? The latest draft that I know is N2912. Is there any more recent one? About that proposal, I’d like to strongly oppose to it.The |nullptr_t| type and the |nullptr| constant, as proposed in N3042
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3042.htm, were
accepted for C23 during the WG14 meeting held in July, 2022. The changes
have been incorporated in the latest working draft, N3047
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3047.pdf.
Thanks for the info and links.
I recommend reading N3042
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3042.htm, and
perhaps the previous papers that introduced the feature to C++, before
concluding that the feature should be removed.
I read it before writing my previous opposing email. I admit I didn’t
read it in full, but a bit fast, before writing it; after writing the
email, I read it again slowly. I confirm my strong rejection to that
change.
This is not the right forum to voice opposition to a WG14 or WG21 proposal.
I know, but I’m not a member of the WG, so this is the best thing I can
try. I specifically CCd in my email a couple of people that participate
in the official discussions (Jens, who wrote N3042, and Joseph, who is a
GCC programmer, and also happened to review that paper). I also guess
some of you also participate in the official discussions. Please
consider these reasons to discuss in your meetings.
Since N3042 https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3042.htm
has already been accepted and WG14 is no longer accepting new papers for
C23, the only way to object to the inclusion of the proposal in C23 is
to file a NB (National Body) comment against the CD (Committee Draft)
during the ballot period.
I hope you can review my opposition based on reasons, and not just
bureaucracy.
No problem, this is a rather obtuse part of the standard so it’s super easy to get the wrong impressions.
I still have to give it plenty more thought before I have any strong opinions on it, FWIW. ![]()
“the address of this object may be a null pointer” confuses me because that’s a contradiction. See C2x 3.15 (the definition of object): region of data storage in the execution environment, the contents of which can represent values. So the address of “this object” can never be a null pointer.
This shows a bit of an ergonomic problem – not everyone writes qualifiers to the right of the the type being qualified, so you’ll also see people write:
optional int q = 4;
int r = q;
which steps pretty squarely into the design space of representing optional values (akin to std::optional - cppreference.com in C++). Just something to keep in mind when playing around with your idea.
I have to think more deeply on qualifying the pointed-to type instead of the pointer type. My initial reaction to it is largely confusion though because this effectively is a qualifier saying “if this object came from a pointer, that pointer could have been null”; all the implementation experience I know of is to instead mark the pointer itself and say “this could be null”.
Hi Aaron,
Alejandro_Colomar: Now, the behavior of nullptr, as I understand it, is that it’s 100% compatible with POSIX’s NULL: it offers the same automatic conversions (there’s a strong differentiation between pointers and integers). So, I don’t see a reason to reinvent the wheel here. That some irresponsible compiler authors decide to implement the null pointer macro NULL to expand to an integer expression shouldn’t be reason enough to force good implementations to adapt to them.It’s incorrect to say that |nullptr| and |NULL| (POSIX or otherwise) are
100% compatible, but they are certainly close. |nullptr| is strongly
typed, |NULL| is weakly typed (it relies on pointer conversions). So you
can spot differences with code like:int i = NULL; // “Fine”, even with POSIX NULL int j = nullptr; // Error |
FWIW, I was one of the people in favor of this proposal in WG14. nullptr
adds some extra type safety benefits over NULL.
Hmm, so the only difference that I notice is that, for nullptr, ISO C
mandates a compiler error, while for NULL, it relies on pointer conversions.
While that is pedantically true, everyone that cares about this issue
will ask their compiler to warn about these issues. For GCC, this means
-Werror=int-conversion (implied by -Wall -Werror, which BTW is the
default in most existing projects).
$ cat null.c
#include <stddef.h>
int *foo(void) { return 0; }
int *bar(void) { return NULL; }
int baz(void) { return NULL; }
$ cc -Wall -Werror -S null.c
null.c: In function ‘baz’:
null.c:4:25: error: returning ‘void *’ from a function with return type
‘int’ makes integer from pointer without a cast [-Werror=int-conversion]
4 | int baz(void) { return NULL; }
> ^~~~
cc1: all warnings being treated as errors
Alejandro_Colomar: I also think that ISO C would do a better job by also deprecating the constant expression 0 to mean a null pointer.I cannot imagine a world in which that’s plausible. It took us 30+ years
to remove K&R C functions, I would imagine deprecating |0| as a null
It took 30 years to remove them, but they were marked as deprecated
since the first ANSI C (IIRC). We could deprecate 0 right now, and
remove in in 30 years or so (or maybe more) if things go well.
My point is that we should try to convince users that the current
differentiation between integers and pointers is good and encourage them
to use it. Not to imply that the current type system is broken and that
we need a new one that is safer; even if we implement it in an opt-in
way that doesn’t break old programs, this is a more breaking change in
the way that C treats pointers, especially to the concept that many
programmers have, and may cause more rejection than redesigning NULL.
If we create yet another alternative to the current null pointer
constants (0, NULL), some will like it and use it, some (including
myself) will consider that since we only care about POSIX we already
have a safe null pointer constant and will continue using NULL, and some
will say that all of this is just nonsense and will continue using 0.
pointer constant would take /at least/ that long. Also, changing C’s
definition of |NULL| to require the cast to |void *| will break code
that’s been valid for a long, long time which is why the committee was
happier to consider adopting the same feature that C++ already has.
I disagree here. ISO C has always defined NULL as a black box macro.
If some code makes any assumption about how NULL is defined by the
implementation, that code can no longer be considered “valid”.
Especially, code that assumes that NULL will be defined as integer 0 is
broken code that should be fixed. POSIX didn’t contradict ISO C in its
definition of NULL, it just defined what ISO C now says is
“implementation-defined”. Redefining “implementation-defined” to
stricter terms (such as requiring that the type of the expression is
‘void *’) cannot be considered breaking changes for valid code.
It
gave us a clean path forward for improving how to represent a null
pointer in C and it did so in a way that helps people who have shared C
and C++ code bases.
Cheers,
Alex
Tom is pointing out that your concerns on this thread will have no impact on the standards body because the proposal was already adopted. The only way to change the standard at this point is through NB comments (because there’s no more opportunities for you to write a paper suggesting a change to this release). We don’t want you to be frustrated thinking the committee isn’t listening because we don’t care about your viewpoint; it’s that we want you to understand just how late your feedback is coming in the cycle and what your options are at this point.
Also: standards work is bureaucracy; there’s no escaping it, unfortunately. ![]()