你能提出至少一个场景,在这个场景中,两种技术之间有显著的差异吗?
union {
T var_1;
U var_2;
}
并且
var_2 = reinterpret_cast<U> (var_1)
我越想越觉得它们从实际角度看起来是一样的。
我发现的一个区别是,虽然联合体的大小与最大数据类型相同,但在这篇文章中描述的reinterpret_cast可能会导致截断,因此普通C风格的联合体甚至比新的C++转换更安全。
你能概述一下这两者之间的差异吗?
你能提出至少一个场景,在这个场景中,两种技术之间有显著的差异吗?
union {
T var_1;
U var_2;
}
并且
var_2 = reinterpret_cast<U> (var_1)
我越想越觉得它们从实际角度看起来是一样的。
我发现的一个区别是,虽然联合体的大小与最大数据类型相同,但在这篇文章中描述的reinterpret_cast可能会导致截断,因此普通C风格的联合体甚至比新的C++转换更安全。
你能概述一下这两者之间的差异吗?
double ntohdouble(const char *buffer) { // [1]
union {
int64_t i;
double f;
} data;
memcpy(&data.i, buffer, sizeof(int64_t));
data.i = ntohll(data.i);
return data.f;
}
double ntohdouble(const char *buffer) { // [2]
int64_t data;
double dbl;
memcpy(&data, buffer, sizeof(int64_t));
data = ntohll(data);
dbl = *reinterpret_cast<double*>(&data);
return dbl;
}
我所知道的所有编译器(gcc、clang、VS、sun、ibm、hp)都认可[1]中的实现,而[2]中的实现则不被认可,并且在使用强制优化时会在某些编译器中出现严重错误。特别地,我见过gcc重新排列指令并在评估ntohl之前读取dbl变量,从而产生错误结果。
(*) 有一点例外,即使原始指针类型是什么,你总是可以从[signed|unsigned] char*
中读取内容。
(+) 再次提醒,如果活动成员与另一个成员共享公共前缀,则可以通过compatible成员读取该前缀。
reinterpret_cast
的情况就是未定义的行为。欲了解更多信息,请谷歌搜索“strict aliasing”,“no-strict-aliasing”或类似内容。 - David Rodríguez - dribeasreinterpret_cast
失效的示例(直接URL)。这两个函数生成相同的汇编输出。 - Maxim Egorushkin一个合适的 union
和一个(假设)合适且安全的 reinterpret_cast
之间存在一些技术上的区别,但我想不出这些区别中有哪些是无法克服的。
我认为,相对于技术原因,更喜欢使用 union
的“真正”原因在于文档说明。
假设您正在设计一堆类来表示一种线路协议(我猜这是使用类型转换的最常见原因),并且该线路协议由许多消息、子消息和字段组成。如果其中一些字段是共同的,例如消息类型、序列号等,则使用 union 简化了将这些元素绑定在一起,并有助于准确记录协议在线路上的出现方式。
使用 reinterpret_cast
同样可以实现此目的,但为了真正了解发生了什么,您必须检查从一个数据包到下一个数据包的代码。使用 union
,您只需查看标头即可了解发生了什么。
union
的情况下提供额外的保证。 - David Rodríguez - dribeasunion U {
int i;
float f;
std::string s;
};
由于 std::string(21.3)声明了所有特殊成员函数的非平凡版本,因此 U 将具有隐式删除的默认构造函数、复制/移动构造函数、复制/移动赋值运算符和析构函数。要使用 U,必须提供一些或全部这些成员函数。- 示例结束]
从实际角度来看,在真实的、非虚构的计算机上,它们很可能是100%相同的。你可以将一个类型的二进制表示形式塞入另一个类型中。
从语言法律专家的角度来看,对于某些情况(例如指针到整数的转换),使用reinterpret_cast
是明确定义的,否则就是特定于实现的。
另一方面,联合类型游戏非常明显是未定义的行为,总是如此(尽管未定义并不一定意味着“不起作用”)。标准规定,最多只能在联合中存储一个非静态数据成员的值。这意味着如果你设置了var1
,那么var1
是有效的,但var2
不是。
然而,由于var1
和var2
存储在同一内存位置,当然你仍然可以随意读写任何类型,并且假设它们具有相同的存储大小,没有任何位被“丢失”。
union
成员共享子成员的公共初始序列,则C++允许此操作。我猜检查所有union
的繁琐性就是实际编译器允许所有联合成员进行类型转换的原因。(更进一步混淆,C委员会似乎已经变得困惑并开始考虑别名,导致了这个混乱:https://dev59.com/s1sW5IYBdhLWcg3wwZcP) - underscore_d
memcpy
完美地解决了这个问题,不需要选择含糊其辞的标准来使其正常工作。 - R. Martinho Fernandes