What Happened to the "Aggregate or Union Type That Includes One of the Aforementioned Types" Strict Aliasing Rule

What happened to the aggregate or union type that includes one of the aforementioned types strict aliasing rule?

The lvalue you use to access the stored value of y is not *reinterpret_cast<Foo*>(&y), of type Foo, but it is reinterpret_cast<Foo*>(&y)->x, which has the type float. Accessing a float using an lvalue of type float is fine. In C++, you can not "access the value of a union or struct" (as whole), you can only access individual members. The rationale you quoted points to a difference between C and C++:

  struct X { int a, b; };
struct X v1 = {1, 2}, v2;
v2 = v1;

In C, the standard says that the assignment loads the value of v1 (as whole) to assign it to v2. Here the values of the objects v1.a and v2.b (both have types int) are accessed using an lvalue of type struct X (which is not int).

In C++, the standard says that the assignment calls the compiler generated assignment operator which is equivalent to

struct X {
...
struct X& operator=(const struct X&other)
{
a = other.a;
b = other.b;
}
};

In this case, calling the assignment operator does not access any value, because the RHS is passed by reference. And executing the assignment operator accesses the two int fields separately (which is fine, even without the aggregate rule), so this is again not accessing a value through an lvalue of type struct X.

Strict aliasing in relation to aggregate or union types

Note 13 in 6.7.2.1 of (I believe) all versions of the standard condones case #1 explicitly. You'll rarely get a clearer answer!
My emphasis

Within a structure object, the non-bit-field members and the units in
which bit-fields reside have addresses that increase in the order in
which they are declared. A pointer to a structure object, suitably
converted, points to its initial member
(or if that member is a
bit-field, then to the unit in which it resides), and vice versa.
There may be unnamed padding within a structure object, but not at its
beginning.

http://port70.net/~nsz/c/c99/n1256.html#6.7.2.1

YES! You can cast a structure to access its first member directly!
Why anyone thinks that's better than &(carray->real) isn't clear. But it's definitely legit.

As another commenter pointed out I discussed case #2 in a previous question.

Aliasing Arrays through structs

It appears in conclusion that case #2 is an OK way to access the member real.
Also iff the structure has no internal padding (platform dependent) you could even access the second member of the array through imag.

I say platform dependent but other investigations have offered no known platform where such a structure would include padding.

What does Note 10 mean in [basic.lval]/11?

This note is language lawyering at its best!

While defining its equivalent rule (§6.5/7 in C99), C bundles in aggregates and union types when describing the valid aliasing possibilities.

C++ doesn't do that. With constructors, conversion operators and inheritance thrown into the mix, classes can be lot more complex than any compound types found in C, so C++ throws up its arms and says, look, we're not going to deal with any of that here.

Instead, "accesses" described by these rules come down to built-in types only. And that's fine, because every compound type is, ultimately, made up of objects of built-in types.

What constitutes valid conversions and reinterpretations of complex types is covered by different rules elsewhere.

This note simply points that out.

The same is described in slightly more detail in the standard's glossary:

[defns.access]: [..] [Note 1: Only objects of scalar type can be accessed. Reads of scalar objects are described in [conv.lval] and modifications of scalar objects are describred in [expr.ass], [expr.post.incr], and [expr.pre.incr]. Attempts to read or modify an object of class type typically invoke a constructor or assignment operator; such invocations do not themselves constitute accesses, although they may involve accesses of scalar subobjects. — end note]

Ultimately, we don't really need to worry about it.

Can a type which is a union member alias that union?

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.

Is it legal to index into a struct?

It is illegal 1. That's an Undefined behavior in C++.

You are taking the members in an array fashion, but here is what the C++ standard says (emphasis mine):

[dcl.array/1]: ...An object of array type contains a contiguously allocated non-empty set of N
subobjects of type T...

But, for members, there's no such contiguous requirement:

[class.mem/17]: ...;Implementation alignment requirements might cause two adjacent
members not to be allocated immediately after each other
...

While the above two quotes should be enough to hint why indexing into a struct as you did isn't a defined behavior by the C++ standard, let's pick one example: look at the expression (&thing.a)[2] - Regarding the subscript operator:

[expr.post//expr.sub/1]:
A postfix expression followed by an expression in square brackets is a
postfix expression. One of the expressions shall be a glvalue of type
“array of T” or a prvalue of type “pointer to T” and the other shall
be a prvalue of unscoped enumeration or integral type. The result is
of type “T”. The type “T” shall be a completely-defined object type.66
The expression E1[E2] is identical (by definition) to ((E1)+(E2))

Digging into the bold text of the above quote: regarding adding an integral type to a pointer type (note the emphasis here)..

[expr.add/4]: When an expression that has integral type is added to or subtracted from a
pointer, the result has the type of the pointer operand. If the
expression P points to element x[i] of an array object x
with n elements, the expressions P + J and J + P (where J has
the value j) point to the (possibly-hypothetical) element x[i + j]
if 0 ≤ i + j ≤ n; otherwise, the behavior is undefined. ...

Note the array requirement for the if clause; else the otherwise in the above quote. The expression (&thing.a)[2] obviously doesn't qualify for the if clause; Hence, Undefined Behavior.


On a side note: Though I have extensively experimented the code and its variations on various compilers and they don't introduce any padding here, (it works); from a maintenance view, the code is extremely fragile. you should still assert that the implementation allocated the members contiguously before doing this. And stay in-bounds :-). But its still Undefined behavior....

Some viable workarounds (with defined behavior) have been provided by other answers.



As rightly pointed out in the comments, [basic.lval/8], which was in my previous edit doesn't apply. Thanks @2501 and @M.M.

1: See @Barry's answer to this question for the only one legal case where you can access thing.a member of the struct via this parttern.

Is the strict aliasing rule incorrectly specified?

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 declared 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. For this not to be the case, it would either have to be that there is some special exemption from the strict aliasing rule that applies in this situation, or that accessing the object via *i does not (also) access the same object as *f.

On union member access via the "." member access operator, the standard 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 useless for optimisation, as just about any two pointers potentially refer to different members of the same union (your example is a case in point).

So at this point, we can say that:

  • the code in your example is explicitly allowed by a non-normative footnote
  • the normative text on the other hand seems to disallow your example (due to strict aliasing), assuming that accessing one member of a union also constitutes access to another - but more on this shortly

Does accessing one member of a union actually access the others, though? If not, the strict aliasing rule isn't concerned with the example. (If it does, the strict aliasing rule, problematically, disallows just about any type-punning via a union).

A union is defined as (6.2.5 para 20):

A union type describes an overlapping nonempty set of member objects

And note that (6.7.2.1 para 16):

The value of at most one of the members can be stored in a union object at any time

Since access is (3):

〈execution-time action〉 to read or modify the value of an object

... and, since non-active union members do not have a stored value, then presumably accessing one member does not constitute access to the other members!

However, the definition of member access (6.5.2.3, quoted above) says "The value is that of the named member" (this is the precise statement that footnote 95 is attached to) - if the member has no value, what then? Footnote 95 gives an answer but as I've noted it is not supported by the normative text.

In any case, nothing in the text would seem to imply that reading or modifying a union member "via the member object" (i.e. directly via an expression using the member access operator) should be any different than reading or modifying it via pointer to that same member. The consensus understanding applied by compiler vendors, which allows them to perform optimisations under the assumption that pointers of different types do not alias, and that requires type punning be performed only via expressions involving member access, is not supported by the text of the standard.

If footnote 95 is considered normative, your example is perfectly fine code without undefined behaviour (unless the value of (*i) is a trap representation), according to the rest of the text. However, if footnote 95 is not considered normative, there is an attempted access to an object which has no stored value and the behaviour then is at best unclear (though the strict aliasing rule is arguably not relevant).

In the understanding of compiler vendors currently, your example has undefined behaviour, but since this isn't specified in the standard it's not clear exactly what constraint the code violates.

Personally, I think the "fix" to the standard is to:

  • disallow access to a non-active union member except via lvalue conversion of a member access expression, or via assignment where the left-hand-side is a member access expression (an exception to this could perhaps be made for when the member in question has character type, since that would not have an effect on possible optimisations due to a similar exception in the strict aliasing rule itself)
  • specify in the normative text that the value of a non-active member is as is currently described by footnote 95

That would make your example not a violation of the strict aliasing rule, but rather a violation of the constraint that a non-active union member must be accessed only via an expression containing the member access operator (and appropriate member).

Therefore, to answer your question - Is the strict aliasing rule incorrectly specified? - no, the strict aliasing rule is not relevant to this example because the objects accessed by the two pointer dereferences are separate objects and, even though they overlap in storage, only one of them has a value at a time. However, the union member access rules are incorrectly specified.

A note on Defect Report 236:

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:

  1. 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. Thus, despite the structural similarity in the example code, the Defect Report is largely unrelated to your question.
  2. "Committee believes that Example 2 violates the aliasing rules in 6.5 paragraph 7" - this indicates that the committee believes that writing a "non-active" union member, but not via an expression containing a member access of the union object, is a strict-aliasing violation. As I've detailed above, this is not supported by the text of the standard.
  3. "In order to not violate the rules, function f in example should be written as" - i.e. you must use the union object (and the "." operator) to change the active member type; this is in agreement with the "fix" to the standard I proposed above.
  4. The Committee Response in DR 236 claims that "Both programs invoke undefined behavior". It has no explanation for why the first does so, and its explanation for why the 2nd does so seems to be wrong.

Type punning in C using union

Yes your reasonning is correct. This is not undefined behavior, but unspecified behavior according to C11, section 6.2.6.1/7 :

When a value is stored in a member of an object of union type, the
bytes of the object representation that do not correspond to that
member but do correspond to other members take unspecified values.

Section 3.19.3 clarifies what this means:

unspecified value: valid value of the relevant type where this International Standard imposes no requirements on which value is
chosen in any instance

This is reminded in Annex J: Portability Issues

J.1 Unspecified behavior

1 The following are unspecified:

— ...

— The value of padding bytes when storing values in structures or
unions (6.2.6.1).

— The values of bytes that correspond to union
members other than the one last stored into (6.2.6.1).

— ...

Nothing about accessing union members is specified in J2 which is about undefined behavior

This being said, portability issues can be severe as section 6.2.6.1/6 reminds:

The value of a structure or union object is never a trap
representation, even though the value of a member of the structure or
union object may be a trap representation.

A trap representation is an "object representation that need not represent a value of the object type" (definition), being understood that "fetching a trap representation might perform a trap but is not required to" (footnote). So accessing the inactive value may lead to the interruption of the programme, but if it doesn't, it's just that there is no guarantee about it.

What is the strict aliasing rule?

A typical situation where you encounter strict aliasing problems is when overlaying a struct (like a device/network msg) onto a buffer of the word size of your system (like a pointer to uint32_ts or uint16_ts). When you overlay a struct onto such a buffer, or a buffer onto such a struct through pointer casting you can easily violate strict aliasing rules.

So in this kind of setup, if I want to send a message to something I'd have to have two incompatible pointers pointing to the same chunk of memory. I might then naively code something like this:

typedef struct Msg
{
unsigned int a;
unsigned int b;
} Msg;

void SendWord(uint32_t);

int main(void)
{
// Get a 32-bit buffer from the system
uint32_t* buff = malloc(sizeof(Msg));

// Alias that buffer through message
Msg* msg = (Msg*)(buff);

// Send a bunch of messages
for (int i = 0; i < 10; ++i)
{
msg->a = i;
msg->b = i+1;
SendWord(buff[0]);
SendWord(buff[1]);
}
}

The strict aliasing rule makes this setup illegal: dereferencing a pointer that aliases an object that is not of a compatible type or one of the other types allowed by C 2011 6.5 paragraph 71 is undefined behavior. Unfortunately, you can still code this way, maybe get some warnings, have it compile fine, only to have weird unexpected behavior when you run the code.

(GCC appears somewhat inconsistent in its ability to give aliasing warnings, sometimes giving us a friendly warning and sometimes not.)

To see why this behavior is undefined, we have to think about what the strict aliasing rule buys the compiler. Basically, with this rule, it doesn't have to think about inserting instructions to refresh the contents of buff every run of the loop. Instead, when optimizing, with some annoyingly unenforced assumptions about aliasing, it can omit those instructions, load buff[0] and buff[1] into CPU registers once before the loop is run, and speed up the body of the loop. Before strict aliasing was introduced, the compiler had to live in a state of paranoia that the contents of buff could change by any preceding memory stores. So to get an extra performance edge, and assuming most people don't type-pun pointers, the strict aliasing rule was introduced.

Keep in mind, if you think the example is contrived, this might even happen if you're passing a buffer to another function doing the sending for you, if instead you have.

void SendMessage(uint32_t* buff, size_t size32)
{
for (int i = 0; i < size32; ++i)
{
SendWord(buff[i]);
}
}

And rewrote our earlier loop to take advantage of this convenient function

for (int i = 0; i < 10; ++i)
{
msg->a = i;
msg->b = i+1;
SendMessage(buff, 2);
}

The compiler may or may not be able to or smart enough to try to inline SendMessage and it may or may not decide to load or not load buff again. If SendMessage is part of another API that's compiled separately, it probably has instructions to load buff's contents. Then again, maybe you're in C++ and this is some templated header only implementation that the compiler thinks it can inline. Or maybe it's just something you wrote in your .c file for your own convenience. Anyway undefined behavior might still ensue. Even when we know some of what's happening under the hood, it's still a violation of the rule so no well defined behavior is guaranteed. So just by wrapping in a function that takes our word delimited buffer doesn't necessarily help.

So how do I get around this?

  • Use a union. Most compilers support this without complaining about strict aliasing. This is allowed in C99 and explicitly allowed in C11.

      union {
    Msg msg;
    unsigned int asBuffer[sizeof(Msg)/sizeof(unsigned int)];
    };
  • You can disable strict aliasing in your compiler (f[no-]strict-aliasing in gcc))

  • You can use char* for aliasing instead of your system's word. The rules allow an exception for char* (including signed char and unsigned char). It's always assumed that char* aliases other types. However this won't work the other way: there's no assumption that your struct aliases a buffer of chars.

Beginner beware

This is only one potential minefield when overlaying two types onto each other. You should also learn about endianness, word alignment, and how to deal with alignment issues through packing structs correctly.

Footnote

1 The types that C 2011 6.5 7 allows an lvalue to access are:

  • a type compatible with the effective type of the object,
  • a qualified version of a type compatible with the effective type of the object,
  • a type that is the signed or unsigned type corresponding to the effective type of the object,
  • a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
  • an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
  • a character type.

Is this type punning well-defined?

You cannot do this: &reinterpret_cast<PunnerToUInt32*>(&x)

The rules on reinterpret_cast state:

When a pointer or reference to object whose dynamic type is DynamicType is reinterpret_cast (or C-style cast) to a pointer or reference to object of a different type AliasedType, the cast always succeeds, but the resulting pointer or reference may only be used to access the object if one of the following is true:

  • AliasedType is (possibly cv-qualified) DynamicType
  • AliasedType and DynamicType are both (possibly multi-level, possibly cv-qualified at each level) pointers to the same type T
  • AliasedType is the (possibly cv-qualified) signed or unsigned variant of DynamicType
  • AliasedType is an aggregate type or a union type which holds one of the aforementioned types as an element or non-static member (including, recursively, elements of subaggregates and non-static data members of the contained unions): this makes it safe to obtain a usable pointer to a struct or union given a pointer to its non-static member or element.
  • AliasedType is a (possibly cv-qualified) base class of DynamicType
  • AliasedType is char or unsigned char: this permits examination of the object representation of any object as an array of unsigned char

Because none of these are true for the combination of DynamicType being float and AliasedType being PunnerToUInt32 the pointer may not be used to access the object, which you are doing. Making the behavior undefined.

For more information see: Why Doesn't reinterpret_cast Force copy_n for Casts between Same-Sized Types?

EDIT:

Breaking down the 4th bullet int bite size chunks yields:

  1. "AliasedType"
    Here taken to be PunnerToUInt32
  2. "is an aggregate type or a union type"
    PunnerToUInt32 qualifies since it meets the qualifications of an aggregate type:

    • array type
    • class type (typically, struct or union), that has

      • no private or protected non-static data members
      • no user-provided constructors, including those inherited from public bases (explicitly defaulted or deleted constructors are allowed)
      • no virtual, private, or protected base classes
      • no virtual member functions
  3. "which holds one of the aforementioned types as an element or non-static member (including, recursively, elements of subaggregates and non-static data members of the contained unions)"
    Again PunnerToUInt32 qualifies because of it's float fl member

  4. "this makes it safe to obtain a usable pointer to a struct or union"
    This is the final correct part as AliassedType is a PunnerToUInt32
  5. "given a pointer to its non-static member or element"
    This is a violation, because the DynamicType which is x is not a member of PunnerToUInt32

Because of the violation of part 5 operating on this pointer is undefined behavior.

If you care for some recommended reading you can check out Empty Base Optimization if not I'll give you the primary relevance here:

Empty base optimization is required for StandardLayoutTypes in order to maintain the requirement that the pointer to a standard-layout object, converted using reinterpret_cast, points to its initial member

Thus you could exploit reinterpret_cast's 4th bullet by doing this:

PunnerToUInt32 x = {13, 42.0F};
auto y = reinterpret_cast<PunnerToUInt32*>(&x.ui32);

Live Example



Related Topics



Leave a reply



Submit