clanguage-lawyernullptrtype-punningc23

Does nullptr_t break type punning or pointer conversions?


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 }; 

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 value nullptr is the same as the object representation of a null pointer value of type void*.


Solution

  • Ok so what if we initialize the void* to some non-zero value?

    C 2023 N3096 7.21.2 3 explicitly answers this. After telling us that the representation of a nullptr value in the nullptr_t type is the same as for a null pointer value in the void * type, it tells us what happens if there is a different sequence of byte values in a nullptr_t object:

    … if the object representation is different, the behavior is undefined.