Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ strict aliasing rule for bit field struct

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;
}
like image 259
Alex Pai Avatar asked May 17 '18 03:05

Alex Pai


1 Answers

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.

like image 65
M.M Avatar answered Nov 01 '22 06:11

M.M