位域结构的 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
对象(当然,只有当 T
是 a
的子对象时才会发生,反之亦然)然后转换的结果是指向 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
的对象,违反了严格的别名规则。
下面的 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
对象(当然,只有当 T
是 a
的子对象时才会发生,反之亦然)然后转换的结果是指向 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 objectb
of typeT
(ignoring cv-qualification) that is pointer-interconvertible witha
, the result is a pointer tob
.
如果我们接受位域是 "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
的对象,违反了严格的别名规则。