FUZxxl FUZxxl - 1 year ago 77
C Question

Is the strict aliasing rule incorrectly specified?

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
does not affect the value of

However, what happens when we pass two pointers to members of the same union? Consider this example, assuming a typical platform where
is an IEEE 754 single precision floating point number and
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,
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.

Answer Source

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 (

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 made only for access via the union member access operator, which means that your example stores via a pointer to a non-existing union member, and thereby commits a strict aliasing violation since it accesses the member that is active using an lvalue of unsuitable type. (I might add that I can not see how the footnote 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).

With this in mind, your example clearly violates the strict aliasing rule if f and i point to the same object (or indeed, if they point to overlapping objects).

There is often confusion caused by another part of the specification, however, also in

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, some people read this as being a general rule for governing type punning; they believe that it implies that it should be possible to use type punning (or at least a limited form of it, based on a common initial sequence) using two pointers to different union members whenever the complete union declaration is visible (since that is what it says 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. It is this glitch in the wording that, I think, makes some people believe 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 (incorrectly) interpret the footnote regarding type punning to apply globally also.

(Incidentally, I am aware of several compilers that do not implement the "global common initial sequence" rule - I assume that their authors do not interpret the specification to imply this rule. I am not aware of any compilers which implement the "global common initial sequence" rule while not also allowing arbitrary type punning).

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 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). 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 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.

(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. I would note that:

  1. "Committee believes that Example 2 violates the aliasing rules in 6.5 paragraph 7" - this doesn't contradict my reasoning above;
  2. "In order to not violate the rules, function f in example should be written as" - this supports my reasoning above; you must use the union object (and the "." operator) to change the active member type, otherwise you are accessing a non-existent member (since the union can contain only one member at a time);
  3. The example in DR 236 is not about type-punning. It is about whether it is ok to assign to a non-active union member via a pointer to that member. The code in question is subtly different to that in the question here, since it does not attempt to access the "original" union member again after writing to the second member.
  4. The Committee Response in DR 236 claims that "Both programs invoke undefined behavior". This however is not supported by the discussion, which shows only that Example 2 invokes undefined behaviour. I believe the response is erroneous.
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download