As previously established, a union of the form
union some_union {
type_a member_a;
type_b member_b;
...
};
with n memb
Starting with your example:
int strict_aliasing_example(int *i, float *f)
{
*i = 1;
*f = 1.0;
return (*i);
}
Let's first acknowledge that, in the absence of any unions, this would violate the strict aliasing rule if i and f both point to the same object; assuming the object has no effective type, then *i = 1 sets the effective type to int and *f = 1.0 then sets it to float, and the final return (*i) then accesses an object with effective type of float via an lvalue of type int, which is clearly not allowed.
The question is about whether this would still amount to a strict-aliasing violation if both i and f point to members of the same union. On union member access via the "." member access operator, the specification says (6.5.2.3):
A postfix expression followed by the . operator and an identifier designates a member of a structure or union object. The value is that of the named member (95) and is an lvalue if the first expression is an lvalue.
The footnote 95 referred to in above says:
If the member used to read the contents of a union object is not the same as the member last used to store a value in the object, the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called ‘‘type punning’’). This might be a trap representation.
This is clearly intended to allow type punning via a union, but it should be noted that (1) footnotes are non-normative, that is, they are not supposed to proscribe behaviour, but rather they should clarify the intention of some part of the text in accordance with the rest of the specification, and (2) this allowance for type punning via a union is deemed by compiler vendors as applying only for access via the union member access operator - since otherwise strict aliasing is pretty meaningless, since just about any potentially aliasing accesses could also be potentially members of the same union.
Your example stores via a pointer to a non-existing or at least non-active union member, and thereby either commits a strict aliasing violation (since it accesses the member that is active using an lvalue of unsuitable type) or uses an lvalue which does not denote an object (since the object corresponding to the non-active member doesn't exist) - it could be argued either way and the standard is not particularly clear, but either interpretation means that your example has undefined behaviour.
(I might add that I can not see how the footnote allowing type-punning via a union describes behavior that is otherwise inherent in the specification - that is, it seems to break the ISO rule of not proscribing behaviour; nothing else in the specification seems to make any allowance for type punning via a union. Furthermore it is something of a stretch to read the normative text as requiring that this form of type punning requires that access must be done immediately via the union type).
There is often confusion caused by another part of the specification, however, also in 6.5.2.3:
One special guarantee is made in order to simplify the use of unions: if a union contains several structures that share a common initial sequence (see below), and if the union object currently contains one of these structures, it is permitted to inspect the common initial part of any of them anywhere that a declaration of the completed type of the union is visible.
Although this does not apply to your example since there is no common initial sequence, I've seen people read this as being a general rule for governing type punning (at least when a common initial sequence is involved); they believe that it implies that it should be possible to use such type punning using two pointers to different union members whenever the complete union declaration is visible (since words to that effect appear in the paragraph quoted above). However, I would point out that the paragraph above still only applies to union member access via the "." operator. The problem with reconciling this understanding is, in that case, that the complete union declaration must anyway be visible, since otherwise you would not be able to refer to the union members. I think that it is this glitch in the wording, combined with similarly bad wording in Example 3 (The following is not a valid fragment (because the union type is not visible ...), when union visibility is not really the deciding factor), that makes some people construe that the common-initial-sequence exception is intended to apply globally, not just for member access via the "." operator, as an exception to the strict aliasing rule; and, having come to this conclusion, a reader might then interpret the footnote regarding type punning to apply globally also, and some do: see the discussion on this GCC bug for example (note that the bug has been in SUSPENDED state for a long time).
(Incidentally, I am aware of several compilers that do not implement the "global common initial sequence" rule. I am not specifically aware of any compilers which implement the "global common initial sequence" rule while not also allowing arbitrary type punning, but that doesn't mean such compilers don't exist. The committee response to Defect Report 257 suggests that they intend the rule to be global, however, I personally think the idea that the mere visibility of a type should change the semantics of code which doesn't refer to that type is deeply flawed, and I know others agree).
At this point you could well question how reading a non-active union member via the member-access operator doesn't violate strict aliasing, if doing the same via a pointer does so. This is again an area where the specification is somewhat hazy; the key is perhaps in deciding which lvalue is responsible for the access. For instance, if a union object u has a member a and I read it via the expression u.a, then we could interpret this as either an access of the member object (a) or as merely an access of the union object (u) which the member value is then extracted from. In the latter case, there is no aliasing violation since it is specifically allowed to access an object (i.e. the active member object) via an lvalue of aggregate type containing a suitable member (6.5¶7). Indeed, the definition of the member access operator in 6.5.2.3 does support this interpretation, if somewhat weakly: the value is that of the named member - while it is potentially an lvalue, it is not necessary to access the object referred to by that lvalue in order to obtain the value of the member, and so strict aliasing violation is avoided. But this is again stretching a little.
(To me it seems under-specified, generally, just when an object has "its stored value accessed ... by an lvalue expression" as per 6.5¶7; we can of course make a reasonable determination for ourselves, but then we must be careful to allow for type-punning via unions as per above, or otherwise be willing to disregard footnote 95. Despite the often unnecessary verbiage, the specification is sometimes lacking in necessary detail).
Arguments about union semantics invariably refer to DR 236 at some point. Indeed, your example code is superficially very similar to the code in that Defect Report. I would note that: