Consider this union:
typedef union
{
void* vptr;
nullptr_t nptr;
} pun_intended;
nullptr_t
is supposedly compatible with void*
1). Ok so what if we initialize the void*
to some non-zero value?
pun_intended foo = { .vptr = (void*)42 };
- This conversion is supposedly legit (impl.defined) as per C23 6.3.2.3 §4, or at least it was until
nullptr_t
was introduced. - And what about union type punning? Also supposedly legit.
- And what about inspecting any type's internal representation in C using a character type pointer, well-defined until C23, 6.3.2.3 §7.
Full example:
#include <stdio.h>
#include <inttypes.h>
#include <stddef.h>
typedef union
{
void* vptr;
nullptr_t nptr;
} pun_intended;
int main(void)
{
pun_intended foo = { .vptr = (void*)42 };
printf("Value: %" PRIuPTR "\n", (uintptr_t)foo.vptr);
if(foo.nptr != (void*)42)
{
puts("It does not have value 42.");
if(foo.nptr == nullptr)
puts("Because it's a nullptr.");
else
puts("But it's not a nullptr.");
unsigned int val = *(unsigned char*)&foo; // little endian assumption here
printf("And it has value %d.\n", val);
if(foo.vptr != nullptr)
{
puts("foo.vptr is however not a nullptr.");
}
}
}
Output on clang 16 -std=c2x:
Value: 42
It does not have value 42
Because it's a nullptr
And it has value 42.
foo.vptr is however not a nullptr
Output on gcc 13.2 -std=c2x:
Value: 42
It does not have value 42.
But it's not a nullptr.
And it has value 42.
foo.vptr is however not a nullptr.
My question: Is anything of the above (which was previously well-defined or impl.defined) now undefined/unspecied behavior? If so, where is that stated? Or are these scenarios simply not considered in C23 - a defect?
1) Source: C23 n3096 draft 7.21.2
The size and alignment of
nullptr_t
is the same as for a pointer to character type. An object representation of the valuenullptr
is the same as the object representation of a null pointer value of typevoid*
.
nullptr_t
is different from all pointer or arithmetic types ... and has exactly one valuenullptr
. Can this be squared with anullptr_t
holding the value 42? – Crippssizeof(void*)
with all bytes set to zero". Otherwise I don't think this type is any improvement over NULL, quite the contrary as it seems to make C less suitable for hardware-related programming. – Coinsurenullptr_t
must have representationnullptr
, then what exactly is "notnullptr
"? A trap representation? – Coinsurenullptr_t
: "It has only a very limited use in contexts where this type is needed to distinguishnullptr
from other expression types", so it's probably not intended to be used in this way. – Zooplastynullptr_t
is supposedly compatible withvoid*
”: Who supposes that and why? “Compatible” has a specific meaning in C. Largely, it means that two types can be completed to be the same type. The fact that one type can be converted to another implicitly does not mean they are compatible. The fact that two types have the same size and representation does not mean they are compatible.nullptr_t
is not compatible withvoid *
. – Dielle_Bool
object that has somehow (for example byunion
type-punning) been set to a value other than 0 or 1, which as far as I can tell would result in UB. That's probably already been asked about in another question. – Hydrantvoid *
andchar *
have the same object representation but are not compatible.short
andint
may have the same object representation but are not compatible in C implementations in which they do. – Diellememcpy
them into each other seamlessly, except some compilers seem to have added diagnostics if you try to pass anullptr_t
to certain functions. If C chose to make artificial restrictions regarding assignment on top of that, that's another story. – Coinsurememcpy
them. That is not what “compatible” means. “Compatible” has a technical definition in the C standard. It is about type semantics, not representation. Largely, two types are compatible if they are the same except for any missing parts. E.g.int []
andint [17]
are compatible because they are both arrays ofint
and differ only in that one is missing the size. – Dielle