4

Reading quote in this answer about strict aliasing rule, I see the following for C++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:

  • ...

  • 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),

  • ...

So I take it to mean that the following code doesn't break strict aliasing rule:

#include <iostream>
#include <cstdint>
#include <climits>
#include <limits>

struct PunnerToUInt32
{
    std::uint32_t ui32;
    float fl;
};

int main()
{
    static_assert(std::numeric_limits<float>::is_iec559 &&
                  sizeof(float)==4 && CHAR_BIT==8,"Oops");
    float x;
    std::uint32_t* p_x_as_uint32=&reinterpret_cast<PunnerToUInt32*>(&x)->ui32;
    *p_x_as_uint32=5;
    std::cout << x << "\n";
}

So OK, strict aliasing rule is satisfied. Does this still exhibit undefined behavior for any other reason?

Community
  • 1
  • 1
Ruslan
  • 18,162
  • 8
  • 67
  • 136
  • 3
    how is the strict aliasing rule satisfied? neither of the types is a char*, which is the only type that gets a free pass.\ – Richard Hodges Jun 23 '16 at 20:12
  • 1
    @RichardHodges You beat me to it. – Jonathan Mee Jun 23 '16 at 20:17
  • @JonathanMee some wrongs simply cannot go unrighted. You get +1 from me – Richard Hodges Jun 23 '16 at 20:20
  • 2
    Rule of thumb: if you see `reinterpret_cast`, and it's not to `char*`, you are looking at strict aliasing violation in 99.9% of the cases (0.1% is reserved for the cases when pointer is not actually dereferenced, but used for another cast back to something which is allowed). – SergeyA Jun 23 '16 at 20:22

2 Answers2

4

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

Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 1
    But isn't `AliasedType=PunnedToUInt32` an aggregate type which holds `DynamicType=float` as a non-static member, as described by bullet #4, thus removing violation? – Ruslan Jun 24 '16 at 04:14
  • @Ruslan I'm sorry I did such a poor job answering your question in the first place. I just didn't get what you were asking until your comment. I've edited, and hopefully you find it clear. If not let me know how I can explain. – Jonathan Mee Jun 24 '16 at 12:25
  • Did you check from the actual standard instead of just from cppreference.com? At least in my copy of C++14 standard, the "this makes it safe to obtain a usable pointer to a struct or union given a pointer to its non-static member or element." part is missing. I'm looking at 3.10.10 in the standard. – khuttun Sep 01 '17 at 12:50
  • @khuttun Can you give me the section name on that. My version of the standard's 3.10 is [defns.dynamic.type.prvalue]. However because of the type aliasing problem I already know that you won't be allowed to `reinterpret_cast` to a non-associated type. – Jonathan Mee Sep 01 '17 at 13:29
  • "3.10 Lvalues and rvalues". The text starts "If a program attempts to access the stored value of an object..." – khuttun Sep 01 '17 at 13:30
  • @khuttun Ah, I see what you're saying, you're saying that http://en.cppreference.com has added that text. They have indeed it's a clarification. The section you're talking about is [6.10\[basic.lval\]8.6](http://eel.is/c++draft/basic.lval#8.6) – Jonathan Mee Sep 01 '17 at 14:45
  • Yes, that's what I was saying. So my point was that reading from the standard vs. reading from en.cppreference.com one would get different answer for this question. – khuttun Sep 02 '17 at 08:33
  • 1
    I started a discussion about this also in cppreference: http://en.cppreference.com/w/Talk:cpp/language/reinterpret_cast – khuttun Sep 02 '17 at 08:42
  • @khuttun Thanks for some really useful commentary here. I'm keeping an eye on the discussion and I'll update if we get anything finalized. In the mean time have a +1 on a random answer by way of thanks. – Jonathan Mee Sep 05 '17 at 16:33
  • I just pushed a complete rewrite of cppreference's strict aliasing discussion yesterday, FYI. – T.C. Sep 30 '17 at 23:55
2

If p_x_as_uint32 were somehow pointing to x1, then *p_x_as_uint32=5 would access an object of type float through a glvalue of type uint32_t, which would result in undefined behavior.

The "access" at issue is the assignment, and all that matters are the type of the glvalue used (uint32_t) and the actual type of the object accessed (float). The tortured series of casts used to obtain the pointer is irrelevant.

It's worth remembering that the strict aliasing rule exists to enable type-based alias analysis. However tortured the route taken is, if you can legally "create a situation where an int* and a float* can simultaneously exist and both can be used to load or store the same memory, you destroy TBAA". If you think the standard's wording somehow allowed you to do this, you are probably wrong, but if you were right, then all you've found is a defect in the standard wording.


1 The class member access is undefined behavior (by omission) because there's no actual PunnerToUInt32 object. However, a class member access is not an "access" within the meaning of the strict aliasing rule; the latter means "to read or modify the value of an object".

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Given three choices: (1) Forbid compilers from assuming aliasing won't occur, even when there's no evidence that it might; (2) Allow compilers to assume aliasing won't occur only in places where there's no evidence that it might; (3) Encourage compilers to assume aliasing won't occur even if there's strong evidence that it does. Which would make the most sense? I'd suggest that the only reason the rules ever got approved is that people expected compiler writers to recognize that #3 was so blindingly stupid that they'd interpret the rules merely as allowing #2. – supercat Sep 30 '17 at 16:39
  • If meaning #3 was intended, and if the authors did not want to deliberately make the language less powerful than it would be without the rules, they would have added directives sufficient to allow programmers to do anything they could do without the rule (e.g. a variation of "placement new" that reinterprets any bit patterns the indicated storage happens to hold). Compilers could allow useful pointer reinterpretation without having to block all optimizations if they recognize pointer reinterpretation as evidence of aliasing *in its immediate vicinity*. I don't see why that's so hard. – supercat Sep 30 '17 at 16:44
  • As a simple principle: optimize aggressively in places where code doesn't do anything weird, but be very cautious, making very few assumptions, when code does weird things. An optimizer which is cautious in places where code does weird things can safely be more aggressive in places where it doesn't, than one which ignores evidence of weirdness. – supercat Sep 30 '17 at 16:47
  • If you have a problem with the rules in the standard, you should write a WG21 paper, preferably with a more exact specification of when aliasing is allowed than "weird". Posting lengthy...commentary...on SO answers that explain what the current rules are doesn't do anything to change them. – T.C. Oct 01 '17 at 00:06