C++ strict aliasing rule for bit field struct
Asked Answered
X

1

7

Does the getValue() member function below violate the c++ strict aliasing rule?

According to the standard, I believe setValue() violates the strict aliasing rule since double is neither an aggregate type nor the base class of IEEE754_64.

What about getValue()? Is it an undefined behavior when the data member is in bit field form as the example below?

I am using similar code in a large project. GCC -O2 and -O3 output the wrong value. And the issue is gone if I add -fno-strict-aliasing. Also, the issue is gone if I use memcpy instead of casting in getValue(). Not sure if it is an GCC bug.

#include <iostream>
#include <cstring>

using namespace std;
struct IEEE754_64
{
  void setValue(double);
  unsigned long long getValue();
  // Data members
  unsigned long long d_mantissa : 52;
  long long d_exponent : 11;
  unsigned long long d_sign : 1;
};

void IEEE754_64::setValue(double d)
{
  (*this) = *reinterpret_cast<IEEE754_64*>(&d);
}

unsigned long long IEEE754_64::getValue()
{
  return * reinterpret_cast<unsigned long long *>(this);
}

int main()
{
  double b = 1.0;
  IEEE754_64 d;

  memcpy(&d, &b, sizeof(double));
  cout<<hex<<d.getValue()<<endl;

  d.setValue(1.0);
  cout<<hex<<d.getValue()<<endl;
  return 0;
}
Xanthochroism answered 17/5, 2018 at 3:2 Comment(0)
B
4

The behaviour of reinterpret_cast<T *>(&a) depends on what object is actually at the memory location of a.

Informally, if there is actually a T object there too (which can, of course, only happen if the T is a subobject of a or vice versa) then the result of the cast is a pointer to the T object.

Otherwise the result of the cast is a pointer to a with the wrong type and reading through it may violate the strict aliasing rule.

Formally, the above is explained in the Standard under the sections [expr.static.cast]/13 , and [basic.compound]/4, see here for detail.


With that in mind, setValue reads a double through an lvalue of type IEEE754_64, there is no doubt that this is a strict aliasing violation.

For the getValue case we have to understand the behaviour of reinterpret_cast<unsigned long long *>(this) which is less straight forward.

According to [basic.compound]/4, an object and its first non-static data member are always pointer-interconvertible. It does not list any exception for bitfields.

But the relevant text from [expr.static.cast]/13 is:

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.

If we accept that the bit-field is "an object of type unsigned long long", it follows that the result of the cast is a pointer to a bit-field. However the Standard does not define the behaviour of pointers to bit-fields.

So, IMHO, the best way to interpret the above text is to say that the bit-field is not an object of type unsigned long long. I believe this is consistent with the rest of the standard; even though there are no prvalues of bit-field type , it definitely does talk about glvalues of bit-field type.

Summing up; I believe the result of reinterpret_cast<unsigned long long *>(this) is NOT a pointer to this->d_mantissa, therefore the getValue() function accesses an object of type IEEE754_64 using a glvalue of type unsigned long long, violating the strict aliasing rule.

Bateman answered 17/5, 2018 at 3:17 Comment(7)
Are you talking about setValue() or getValue() or both? I knew that the setValue() violates the strict aliasing rule. Does the getValue() also violate it?Xanthochroism
The standard says "If a program attempts to access the stored value of an object through an lvalue 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 members (including, recursively, a member of a subaggregate or contained union),...".Xanthochroism
And the unsigned long long is one of the data member type in IEEE754_64.Xanthochroism
Ah.. You are right. I misunderstood the description in the standards. So it is still a violation if I remove the bit filed as below, right? struct IEEE754_64 { unsigned long long d_mantissa; long long d_exponent; unsigned long long d_sign; };Xanthochroism
@AlexPai I changed my answer now; let's delete our old commentsBateman
Interestingly, the C Standard (C11 6.7.2.1/15) defines that a pointer to struct can actually be cast to point to the underlying unit of a bit-field which is its first member! I can't find any equivalent text in the C++ Standard but I may be overlooking something.Bateman
Just pointing out that the standard does not define the actual layout of the bitfield. Even if the aliasing was itself allowed, and double is known to have the IEEE754 layout, there'd still be no guarantee that the bit field maps to the right bits. Also, you should point out that, as in many other cases, memcpy can be used to eliminate the strict aliasing violation (and compilers are generally smart enough to not actually copy data). Basically, memcpy is treated as an (allowed) access through a pointer to cv-qualified char.Borders

© 2022 - 2024 — McMap. All rights reserved.