C++ unions vs. reinterpret_cast

C++ unions vs. reinterpret_cast

本文关键字:cast reinterpret vs unions C++      更新时间:2023-10-16

从其他StackOverflow问题和阅读ISO/IEC c++标准草案& section;9.5.1看来,使用联合对数据进行文字reinterpret_cast是未定义的行为。

考虑下面的代码。目标是取0xffff的整数值,并将其逐字解释为IEEE 754浮点数中的一系列位。(二进制转换直观地显示了如何完成。)

#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 是否更好?

标准在& section;9.5.1中声明如下:

在union中,在任何时候最多只能有一个非静态数据成员处于活动状态,也就是说,在任何时候最多只能有一个非静态数据成员的值存储在union中。[…union的大小足以包含其最大的非静态数据成员。分配每个非静态数据成员就好像它是结构体的唯一成员一样。联合对象的所有非静态数据成员具有相同的地址。

最后一句话,保证所有非静态成员有相同的地址,似乎表明联合的使用保证reinterpret_cast的使用相同,但前面关于活动数据成员的语句似乎排除了这种保证。

那么哪个结构更正确呢?

编辑:

使用英特尔的icpc编译器,上面的代码产生了更有趣的结果:

$ icpc union.cpp
$ ./a.out
size of int    4
size of float  4
myInt          65535
myFloat        0
theFloat       0

未定义的原因是无法保证intfloat的值表示究竟是什么。c++标准并没有说float被存储为IEEE 754单精度浮点数。对于将值为0xffffint对象处理为float,标准应该怎么说?它没有说明任何东西,除了它是未定义的事实。

实际上,这就是reinterpret_cast的目的——告诉编译器忽略它所知道的关于对象类型的一切,并相信这个int实际上是一个float。它几乎总是用于特定于机器的位级欺诈。c++标准只是不能保证你一旦做了什么。在这种情况下,由您来准确地理解编译器和机器的操作。

对于unionreinterpret_cast方法都是如此。我建议reinterpret_cast更适合这个任务,因为它使意图更清晰。然而,保持代码良好定义始终是最好的方法。

这不是未定义行为。它是实现定义的行为。第一个确实意味着不好的事情会发生。另一种方法是指将发生的事情必须由实现来定义。

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,类型双关也会在icpc上工作。

未定义的行为并不意味着坏事一定会发生。这意味着只有,语言定义没有告诉您发生了什么。这种类型的双关自远古以来(即自1969年以来)一直是C和c++编程的一部分;只有特别反常的实现者才会编写出这样的编译器