从other StackOverflow questions开始,并且阅读ISO/IEC draft C++ standard标准的§9.5.1,使用联合来执行文字reinterpret_cast
数据是未定义的行为。
请考虑以下代码。目标是取整数值0xffff
并将其解释为IEEE 754浮点中的一系列位。 (Binary convert shows visually how this is done.)
#include <iostream>
using namespace std;
union unionType {
int myInt;
float myFloat;
};
int main() {
int i = 0xffff;
unionType u;
u.myInt = i;
cout << "size of int " << sizeof(int) << endl;
cout << "size of float " << sizeof(float) << endl;
cout << "myInt " << u.myInt << endl;
cout << "myFloat " << u.myFloat << endl;
float theFloat = *reinterpret_cast<float*>(&i);
cout << "theFloat " << theFloat << endl;
return 0;
}
预计使用GCC和clang编译器输出此代码。
size of int 4
size of float 4
myInt 65535
myFloat 9.18341e-41
theFloat 9.18341e-41
我的问题是,该标准是否真的排除了myFloat
的确定性价值?是否以任何方式使用reinterpret_cast
更好来执行此类转换?
标准规定了§9.5.1中的以下内容:
在联合中,最多只有一个非静态数据成员可以随时处于活动状态,也就是说,最多一个非静态数据成员的值可以存储在一个联合中任何时候。 [...]联合的大小足以包含其最大的非静态数据成员。每个非静态数据成员都被分配,就好像它是结构的唯一成员一样。 union对象的所有非静态数据成员都具有相同的地址。
保证所有非静态成员具有相同地址的最后一句似乎表明使用保证的联合与使用reinterpret_cast
相同,但早先关于活跃数据成员的陈述似乎排除了这种保证。
那么哪种结构更正确?
修改
使用英特尔的icpc
编译器,上面的代码产生了更有趣的结果:
$ icpc union.cpp
$ ./a.out
size of int 4
size of float 4
myInt 65535
myFloat 0
theFloat 0
答案 0 :(得分:9)
未定义的原因是因为无法保证int
和float
的值的确切含义。 C ++标准没有说float
存储为IEEE 754单精度浮点数。标准对于将int
对象的值0xffff
视为float
而言究竟应该说些什么?除了未定义的事实外,它没有说什么。
然而,实际上,这是reinterpret_cast
的目的 - 告诉编译器忽略它所知道的关于对象类型的所有内容,并相信这个int
实际上是float
。它几乎总是用于机器特定的位级jiggery-pokery。一旦你这样做,C ++标准就不能保证你的任何东西。那时,您可以自行了解编译器和机器在这种情况下的确切行为。
对于union
和reinterpret_cast
方法都是如此。我建议reinterpret_cast
对于这项任务来说“更好”,因为它使意图更清晰。但是,保持代码定义良好始终是最好的方法。
答案 1 :(得分:7)
这不是未定义的行为。它是实现定义的行为。第一个意味着坏事可能发生。另一个意味着将要发生的事情必须由实施来定义。
reinterpret_cast违反了严格的别名规则。所以我认为它不会可靠地运作。联合技巧是人们称之为type-punning
并且通常由编译器允许的。 gcc人员记录了编译器的行为:http://gcc.gnu.org/onlinedocs/gcc/Structures-unions-enumerations-and-bit_002dfields-implementation.html#Structures-unions-enumerations-and-bit_002dfields-implementation
我认为这也适用于icpc(但它们似乎没有记录它们是如何实现的)。但是当我查看程序集时,它看起来像icc试图欺骗浮动并使用更高精度的浮点数。将-fp-model source
传递给编译器修复了该问题。使用该选项,我得到与gcc相同的结果。
我认为你一般不想使用这个标志,这只是验证我的理论的一个测试。
因此,对于icpc,我认为如果将代码从int / float切换为long / double,则type-punning也适用于icpc。
答案 2 :(得分:1)
未定义的行为并不意味着必须发生坏事。这意味着仅语言定义不会告诉您发生了什么。这种类型的双关语自古以来就是C和C ++程序设计的一部分(即自1969年以来);如果编译器不能正常工作,那么需要一个特别反常的实现者。