位域结构的 C++ 严格别名规则

C++ strict aliasing rule for bit field struct

下面的 getValue() 成员函数是否违反了 c++ 严格的别名规则?

根据标准,我认为 setValue() 违反了严格的别名规则,因为 double 既不是聚合类型也不是 IEEE754_64 的基础 class。

getValue() 呢?当数据成员是位字段形式时是否是未定义的行为,如下例?

我正在一个大型项目中使用类似的代码。 GCC -O2 和 -O3 输出错误值。如果我添加 -fno-strict-aliasing,问题就消失了。此外,如果我使用 memcpy 而不是在 getValue() 中强制转换,问题就消失了。不确定这是否是 GCC 错误。

#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;
}

reinterpret_cast<T *>(&a) 的行为取决于 a 的内存位置实际是什么对象。

非正式地,如果实际上也有一个 T 对象(当然,只有当 Ta 的子对象时才会发生,反之亦然)然后转换的结果是指向 T 对象的指针。

否则,转换的结果是指向 a 的指针,类型错误,读取它可能会违反严格的别名规则。

形式上,上述内容在标准中的 [expr.static.cast]/13 部分和 [basic.compound]/4, 部分进行了详细说明。


考虑到这一点,setValue 通过 IEEE754_64 类型的左值读取 double,毫无疑问,这是一个严格的别名违规。

对于 getValue 的情况,我们必须了解 reinterpret_cast<unsigned long long *>(this) 的行为,这不太直接。

根据 [basic.compound]/4,对象及其第一个非静态数据成员始终是指针可相互转换的。它没有列出位域的任何例外情况。

但是来自 [expr.static.cast]/13 的相关文本是:

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.

如果我们接受位域是 "an object of type unsigned long long",则强制转换的结果是指向位域的指针。然而,该标准没有定义指向位域的指针的行为。

所以,恕我直言,解释上述文本的最佳方式是说位域不是 unsigned long long 类型的对象。我相信这与标准的其余部分是一致的;即使没有位域类型的纯右值,它也肯定会谈论位域类型的左值。

总结;我相信 reinterpret_cast<unsigned long long *>(this) 的结果不是指向 this->d_mantissa 的指针,因此 getValue() 函数使用类型 unsigned long long 的泛左值访问类型 IEEE754_64 的对象,违反了严格的别名规则。