Is this type punning well defined?

Viewed 1159

I have structure like below.

struct result{
    int a;
    int b;
    int c;
    int d;
}

and union like below.

union convert{
   int arr[4];
   struct result res;
}

and I type pun as below.

  int arr1[4] = {1,2,3,5};
  union convert *pointer = (union convert *) arr1; // Here is my question, is it well defined?

  printf("%d %d\n", pointer->res.a, pointer->res.b);
4 Answers

pointer->res.a is fine but the behaviour of pointer->res.b is undefined.

There could be an arbitrary amount of padding between the a and b members.

Some compilers allow you to specify that there is no padding between members but of course then you are giving up portability.

Is this type punning well defined?

struct result{
    int a,b,c,d;
}

union convert {
   int arr[4];
   struct result res;
}

int arr1[4] = {1,2,3,5};
union convert *pointer = (union convert *) arr1; 

(union convert *) arr1 risks alignment failure.

A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined. C11dr §6.3.2.3 8

There is no requirement that union convert and int share the same alignment. union convert requirements may exceed int for example.

Consider this possibility: arr1[] lives on int street where all addresses are multiple of 4. union and struct friends lives on "multiple of 8" street. arr1[] might have address 0x1004 (not a multiple of 8).

In 2019, alignment failures are more commonly seen with char (needing 1) and other types needing 2 or more. In OP's select case, I doubt a real platform will have alignment issues, yet incorrect alignment remains possible.

This type punning is not well defined.


Additional concerns

Other answers and comments discuss padding issues, which further identifies trouble.

@Eric Postpischil comment about improper access with pointer->res.a adds more reasons to consider this UB.

C imposes no rule about how much padding is left between 2 consecutive members of a structure.

This is why the implementations define many #pragma directives -- specially to change this behaviour.

So, as the answer of Bathsheba says, ...->b is undefined.

I answered the very same question some time ago, here.

Pointer punning is not safe. Use real union punning instead.

Assumptions: the struct is properly packed (no padding between the members)

#include <stdio.h>
#include <string.h>



struct __attribute__((packed)) result{
    int a;
    int b;
    int c;
    int d;
};

union convert{
   int arr[4];
   struct result res;
};

  volatile int arr1[4];

void foo(void)
{

  union convert cnv;

  memcpy(&cnv, (void *)arr1, sizeof(arr1));

  printf("%d %d\n", cnv.res.a, cnv.res.b);
}

all modern compilers will optimize out the memcpy call

https://godbolt.org/z/4qtRIF

.LC0:
        .string "%d %d\n"
foo:
        mov     rsi, QWORD PTR arr1[rip]
        xor     eax, eax
        mov     rdi, QWORD PTR arr1[rip+8]
        mov     edi, OFFSET FLAT:.LC0
        mov     rdx, rsi
        sar     rdx, 32
        jmp     printf
Related