Prompted by this question:
The C11 standard states that a pointer to a union can be converted to a pointer to each of its members. From Section 6.7.2.1p17:
The size of a union is sufficient to contain the largest of its members. The value of at most one of the members can be stored in a union object at any time. A pointer to a union object, suitably converted, points to each of its members (or if a member is a bit-field, then to the unit in which it resides), and vice versa.
This implies you can do the following:
union u {
int a;
double b;
};
union u myunion;
int *i = (int *)&u;
double *d = (double *)&u;
u.a = 2;
printf("*i=%d\n", *i);
u.b = 3.5;
printf("*d=%f\n", *d);
But what about the reverse: in case of the above union, can an int *
or double *
be safely converted to a union u *
? Consider the following code:
#include <stdio.h>
union u {
int a;
double b;
};
void f(int isint, union u *p)
{
if (isint) {
printf("int value=%d\n", p->a);
} else {
printf("double value=%f\n", p->b);
}
}
int main()
{
int a = 3;
double b = 8.25;
f(1, (union u *)&a);
f(0, (union u *)&b);
return 0;
}
In this example, pointers to int
and double
, both of which are members of union u
, are passed to a function where a union u *
is expected. A flag is passed to the function to tell it which "member" to access.
Assuming, as in this case, that the member accessed matches the type of the object that was actually passed in, is the above code legal?
I compiled this on gcc 6.3.0 with both -O0
and -O3
and both gave the expected output:
int value=3
double value=8.250000
Regarding strict aliasing, there is not an issue going from pointer-to-type (for example &a
), to pointer-to-union containing that type. It is one of the exceptions to the strict aliasing rule, C17 6.5/7:
An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
- a type compatible with the effective type of the object, /--/
- an aggregate or union type that includes one of the aforementioned types among its members
So this is fine as far as strict aliasing goes, as long as the union
contains an int
/double
. And the pointer conversion in itself is well-defined too.
The problem comes when you try to access the contents, for example the contents of an int
as a larger double
. This is probably UB for multiple reasons - I can think of at least C17 6.3.2.3/7:
A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned69) for the referenced type, the behavior is undefined.
Where the non-normative foot note provides more information:
69) In general, the concept “correctly aligned” is transitive: if a pointer to type A is correctly aligned for a pointer to type B, which in turn is correctly aligned for a pointer to type C, then a pointer to type A is correctly aligned for a pointer to type C.