As previously established, a union of the form
union some_union {
type_a member_a;
type_b member_b;
...
};
with n members comprises n + 1 objects in overlapping storage: One object for the union itself and one object for each union member. It is clear, that you may freely read and write to any union member in any order, even if reading a union member that was not the last one written to. The strict aliasing rule is never violated, as the lvalue through which you access the storage has the correct effective type.
This is further supported by footnote 95, which explains how type punning is an intended use of unions.
A typical example of the optimizations enabled by the strict aliasing rule is this function:
int strict_aliasing_example(int *i, float *f)
{
*i = 1;
*f = 1.0;
return (*i);
}
which the compiler may optimize to something like
int strict_aliasing_example(int *i, float *f)
{
*i = 1;
*f = 1.0;
return (1);
}
because it can safely assume that the write to *f
does not affect the value of *i
.
However, what happens when we pass two pointers to members of the same union? Consider this example, assuming a typical platform where float
is an IEEE 754 single precision floating point number and int
is a 32 bit two's complement integer:
int breaking_example(void)
{
union {
int i;
float f;
} fi;
return (strict_aliasing_example(&fi.i, &fi.f));
}
As previously established, fi.i
and fi.f
refer to an overlapping memory region. Reading and writing them is unconditionally legal (writing is only legal once the union has been initialized) in any order. In my opinion, the previously discussed optimization performed by all major compilers yields incorrect code as the two pointers of different type legally point to the same location.
I somehow can't believe that my interpretation of the strict aliasing rule is correct. It doesn't seem plausible that the very optimization the strict aliasing was designed for is not possible due to the aforementioned corner case.
Please tell me why I'm wrong.
A related question turned up during research.
Please read all existing answers and their comments before adding your own to make sure that your answer adds a new argument.
Under the definition of union members in §6.5.2.3:
See also §6.2.3 ¶1:
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
isfloat
, but its stored value is accessed by an lvalue of typeint
(see §6.5 ¶7).Note: All references cite this C11 standard draft.
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 assumef
andi
point to different addresses.The
breaking_example()
causes the two pointers passed tostrict_aliasing_example()
to point to the same address. This breaks the assumption thatstrict_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 tostrict_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 withinstrict_aliasing_example()
.Let's back away from the standard for a second, and think about what's actually possible for a compiler.
Suppose that
strict_aliasing_example()
is defined instrict_aliasing_example.c
, andbreaking_example()
is defined inbreaking_example.c
. Assume both of these files are compiled separately and then linked together, like so:Of course we'll have to add a function prototype to
breaking_example.c
, which looks like this:int strict_aliasing_example(int *i, float *f);
Now consider that the first two invocations of
gcc
are completely independent and cannot share information except for the function prototype. It is impossible for the compiler to know thati
andj
will point to members of the same union when it generates code forstrict_aliasing_example()
. There's nothing in the linkage or type system to specify that these pointers are somehow special because they came from a union.This supports the conclusion that other answers have mentioned: from the standard's point of view, accessing a union via
.
or->
obeys different aliasing rules compared with dereferencing an arbitrary pointer.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
also violates N1570 6.5p7 because
foo.x
is an lvalue of typeint
, it is used to access the stored value of an object of typestruct foo
, and typeint
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:
It may be reasonable for a compiler to fail to recognize the association between
p1
andunionArr[i].v1
if other actions involvingunionArr[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.
Starting with your example:
Let's first acknowledge that, in the absence of any unions, this would violate the strict aliasing rule if
i
andf
both point to the same object; assuming the object has no effective type, then*i = 1
sets the effective type toint
and*f = 1.0
then sets it tofloat
, and the finalreturn (*i)
then accesses an object with effective type offloat
via an lvalue of typeint
, which is clearly not allowed.The question is about whether this would still amount to a strict-aliasing violation if both
i
andf
point to members of the same union. On union member access via the "." member access operator, the specification says (6.5.2.3):The footnote 95 referred to in above says:
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:
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 membera
and I read it via the expressionu.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:
The C11 standard (§6.5.2.3.9 EXAMPLE 3) has following example:
But I can't find more clarification on this.