您实际上违反了这些重新解释强制转换的严格别名规则(C++ 标准的第 3.10 节)。当您打开编译器优化时,这可能会在您的脸上爆炸。
C++ 标准,第 3.10 节第 15 段说:
如果程序尝试通过以下类型之一以外的左值访问对象的存储值,则行为未定义
- 对象的动态类型,
- 对象的动态类型的 cv 限定版本,
- 类似于对象的动态类型的类型,
- 与对象的动态类型相对应的有符号或无符号类型,
- 对应于对象动态类型的 cv 限定版本的有符号或无符号类型,
- 聚合或联合类型,在其成员中包含上述类型之一(递归地包括子聚合或包含联合的成员),
- 一个类型,它是对象的动态类型的(可能是 cv 限定的)基类类型,
- char 或 unsigned char 类型。
具体来说,3.10/15 不允许我们通过 unsigned int 类型的左值访问浮点对象。我真的被这个咬了。我写的程序在开启优化后停止工作。显然,GCC 并不期望 float 类型的左值别名 int 类型的左值,这是 3.10/15 的合理假设。优化器在利用 3.10/15 的 as-if 规则下对指令进行了洗牌,并且它停止了工作。
在以下假设下
- float 真的对应一个 32bit IEEE-float,
- sizeof(float)==sizeof(int)
- unsigned int 没有填充位或陷阱表示
你应该可以这样做:
/// returns a 30 bit number
unsigned int pack_float(float x) {
unsigned r;
std::memcpy(&r,&x,sizeof r);
return r >> 2;
}
float unpack_float(unsigned int x) {
x <<= 2;
float r;
std::memcpy(&r,&x,sizeof r);
return r;
}
这不会受到“3.10-违规”的影响,并且通常非常快。至少 GCC 将 memcpy 视为一个内在函数。如果您不需要这些函数来处理具有极高幅度的 NaN、无穷大或数字,您甚至可以通过将“r >> 2”替换为“(r+1) >> 2”来提高准确性:
unsigned int pack_float(float x) {
unsigned r;
std::memcpy(&r,&x,sizeof r);
return (r+1) >> 2;
}
即使由于尾数溢出而改变指数也有效,因为 IEEE-754 编码将连续浮点值映射到连续整数(忽略 +/- 零)。这种映射实际上非常接近对数。