Search code examples
c++language-lawyerstrict-aliasingreinterpret-cast

Is it a strict aliasing violation to alias a struct as its first member?


Sample code:

struct S { int x; };

int func()
{
     S s{2};
     return (int &)s;    // Equivalent to *reinterpret_cast<int *>(&s)
}

I believe this is common and considered acceptable. The standard does guarantee that there is no initial padding in the struct. However this case is not listed in the strict aliasing rule (C++17 [basic.lval]/11):

If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:

  • (11.1) the dynamic type of the object,
  • (11.2) a cv-qualified version of the dynamic type of the object,
  • (11.3) a type similar (as defined in 7.5) to the dynamic type of the object,
  • (11.4) a type that is the signed or unsigned type corresponding to the dynamic type of the object,
  • (11.5) a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
  • (11.6) an aggregate or union type that includes one of the aforementioned types among its elements or non-static data members (including, recursively, an element or non-static data member of a subaggregate or contained union),
  • (11.7) a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
  • (11.8) a char, unsigned char, or std::byte type.

It seems clear that the object s is having its stored value accessed.

The types listed in the bullet points are the type of the glvalue doing the access, not the type of the object being accessed. In this code the glvalue type is int which is not an aggregate or union type, ruling out 11.6.

My question is: Is this code correct, and if so, under which of the above bullet points is it allowed?


Solution

  • The behaviour of the cast comes down to [expr.static.cast]/13;

    A prvalue of type “pointer to cv1 void” can be converted to a prvalue of type “pointer to cv2 T”, where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. If the original pointer value represents the address A of a byte in memory and A does not satisfy the alignment requirement of T , then the resulting pointer value is unspecified. Otherwise, if the original pointer value points to an object a, and there is an object b of type T (ignoring cv-qualification) that is pointer-interconvertible with a, the result is a pointer to b. Otherwise, the pointer value is unchanged by the conversion.

    The definition of pointer-interconvertible is:

    Two objects a and b are pointer-interconvertible if:

    • they are the same object, or
    • one is a union object and the other is a non-static data member of that object, or
    • one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members, the first base class subobject of that object, or
    • there exists an object c such that a and c are pointer-interconvertible, and c and b are pointer-interconvertible.

    So in the original code, s and s.x are pointer-interconvertible and it follows that (int &)s actually designates s.x.

    So, in the strict aliasing rule, the object whose stored value is being accessed is s.x and not s and so there is no problem, the code is correct.