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

时间:2018-05-17 03:02:50

标签: c++ bit-fields strict-aliasing

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

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

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

1 个答案:

答案 0 :(得分:4)

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

非正式地,如果实际上存在T个对象(当然,只有当Ta的子对象或反之亦然时才会发生),那么结果cast的指针是指向T对象的指针。

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

正式地,上述内容在标准[expr.static.cast] / 13和[basic.compound] / 4,see here部分详细解释。

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

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

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

但[expr.static.cast] / 13的相关文字是:

  

否则,如果原始指针值指向对象a,并且存在类型为b的对象T(忽略cv-qualification),该对象可与{指针互连。 {1}},结果是指向a

的指针

如果我们接受位字段是“b类型的对象”,那么强制转换的结果就是指向位字段的指针。但是,标准没有定义指向位字段的指针的行为。

所以,恕我直言,解释上述文本的最佳方式是说比特字段不是unsigned long long类型的对象。我相信这与标准的其他部分一致;即使没有位字段类型的prvalues,它肯定会讨论位字段类型的glvalues。

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