As previously established, a union of the form
union some_union {
type_a member_a;
type_b member_b;
...
};
with n memb
The Standard does not allow the stored value of a struct or union to be accessed using an lvalue of the member type. Since your example accesses the stored value of a union using lvalues whose type is not that of the union, nor any type that contains that union, behavior would be Undefined on that basis alone.
The one thing that gets tricky is that under a strict reading of the Standard, even something so straightforward as
int main(void)
{
struct { int x; } foo;
foo.x = 1;
return 0;
}
also violates N1570 6.5p7 because foo.x
is an lvalue of type int
, it is used to access the stored value of an object of type struct foo
, and type int
does not satisfy any of the conditions on that section.
The only way the Standard can be even remotely useful is if one recognizes that there need to be exceptions to N1570 6.5p7 in cases involving lvalues that are derived from other lvalues. If the Standard were to describe cases where compilers may or must recognize such derivation, and specify that N1570 6.5p7 only applies in cases where storage is accessed using more than one type within a particular execution of a function or loop, that would have eliminated a lot of complexity including any need for the notion of "Effective Type".
Unfortunately, some compilers have taken it upon themselves to ignore derivation of lvalues and pointers even in some obvious cases like:
s1 *p1 = &unionArr[i].v1;
p1->x ++;
It may be reasonable for a compiler to fail to recognize the association between p1
and unionArr[i].v1
if other actions involving unionArr[i]
separated the creation and use of p1, but neither gcc nor clang can consistently recognize such association even in simple cases where the use of the pointer immediately follows the action which takes the address of the union member.
Again, since the Standard doesn't require that compilers recognize any usage of derived lvalues unless they are of character types, the behavior of gcc and clang does not make them non-conforming. On the other hand, the only reason they are conforming is because of a defect in the Standard which is so outrageous that nobody reads the Standard as saying what it actually does.
Essentially the strict aliasing rule describes circumstances in which a compiler is permitted to assume (or, conversely, not permitted to assume) that two pointers of different types do not point to the same location in memory.
On that basis, the optimisation you describe in strict_aliasing_example()
is permitted because the compiler is allowed to assume f
and i
point to different addresses.
The breaking_example()
causes the two pointers passed to strict_aliasing_example()
to point to the same address. This breaks the assumption that strict_aliasing_example()
is permitted to make, therefore results in that function exhibiting undefined behaviour.
So the compiler behaviour you describe is valid. It is the fact that breaking_example()
causes the pointers passed to strict_aliasing_example()
to point to the same address which causes undefined behaviour - in other words, breaking_example()
breaks the assumption that the compiler is allowed to make within strict_aliasing_example()
.
Under the definition of union members in §6.5.2.3:
3 A postfix expression followed by the
.
operator and an identifier designates a member of a structure or union object. ...4 A postfix expression followed by the
->
operator and an identifier designates a member of a structure or union object. ...
See also §6.2.3 ¶1:
- the members of structures or unions; each structure or union has a separate name space for its members (disambiguated by the type of the expression used to access the member via the
.
or->
operator);
It is clear that footnote 95 refers to the access of a union member with the union in scope and using the .
or ->
operator.
Since assignments and accesses to the bytes comprising the union are not made through union members but through pointers, your program does not invoke the aliasing rules of union members (including those clarified by footnote 95).
Further, normal aliasing rules are violated since the effective type of the object after *f = 1.0
is float
, but its stored value is accessed by an lvalue of type int
(see §6.5 ¶7).
Note: All references cite this C11 standard draft.