【问题标题】:Why cast to a pointer then dereference?为什么要转换为指针然后取消引用?
【发布时间】:2016-09-03 23:04:18
【问题描述】:

我正在浏览这个示例,该示例具有输出十六进制位模式以表示任意浮点数的函数。

void ExamineFloat(float fValue)
{
    printf("%08lx\n", *(unsigned long *)&fValue);
}

为什么取fValue的地址,转换成无符号长指针,然后解引用?所有这些工作不都等同于直接转换为 unsigned long 吗?

printf("%08lx\n", (unsigned long)fValue);

我试过了,答案不一样,很困惑。

【问题讨论】:

  • 这是未定义的行为。这是人们在 1989 年 C 被标准化之前所做的事情,并且有些人没有跟上时代

标签: c++ c casting


【解决方案1】:
(unsigned long)fValue

这会根据“通常的算术转换”将float 值转换为unsigned long 值。

*(unsigned long *)&fValue

这里的意图是获取存储fValue的地址,假设在这个地址上没有float而是unsigned long,然后读取unsigned long。目的是检查用于在内存中存储float 的位模式。

如图所示,这会导致未定义的行为。

原因:您不能通过指向与对象类型不“兼容”的类型的指针来访问对象。 “兼容”类型例如是 (unsigned) char 和其他所有类型,或者共享相同初始成员的结构(这里说的是 C)。有关详细的 (C11) 列表,请参阅 §6.5/7 N1570请注意,我对“兼容”的使用与参考文本中的不同 - 更广泛。

解决方案:转换为unsigned char *,访问对象的各个字节并从中组装一个unsigned long

unsigned long pattern = 0;
unsigned char * access = (unsigned char *)&fValue;
for (size_t i = 0; i < sizeof(float); ++i) {
  pattern |= *access;
  pattern <<= CHAR_BIT;
  ++access;
}

请注意(正如@CodesInChaos 指出的那样)上面将浮点值视为首先存储其最高有效字节(“big endian”)。如果您的系统对浮点值使用不同的字节顺序,您需要进行调整(或重新排列上述unsigned long 的字节,对您来说更实用)。

【讨论】:

  • 在 C++ 中是否允许/定义 reinterpret_cast&lt;unsigned long&amp;&gt;(fValue)(当然,假设类型大小合适)?
  • 只要浮点数和整数的字节序相同(忽略 UB),原始代码就可以工作。您的代码采用大端序。我会在uint32_t 中使用memcpy(以及匹配大小的断言)。
  • @celtschk 如果实际使用该引用不会被视为严格的别名违规,我会感到非常惊讶。 -- "T1 类型的左值表达式可以转换为对另一种类型 T2 的引用。结果是左值或 xvalue 引用与原始左值相同的对象,但类型不同。不创建临时值,也不创建副本生成时,不调用构造函数或转换函数。只有在类型别名规则允许的情况下才能安全地访问结果引用“(src)
【解决方案2】:

浮点值具有内存表示:例如,字节可以使用IEEE 754 表示浮点值。

第一个表达式*(unsigned long *)&amp;fValue 将解释这些字节,就好像它是unsigned long 值的表示。事实上,在 C 标准中,它会导致未定义的行为(根据所谓的“严格别名规则”)。在实践中,需要考虑字节顺序等问题。

第二个表达式(unsigned long)fValue 符合C 标准。它有一个确切的含义:

C11 (n1570),§ 6.3.1.4 实浮点数和整数

当实浮点类型的有限值转换为_Bool以外的整数类型时,小数部分将被丢弃(即,该值被截断为零)。如果整数部分的值不能用整数类型表示,则行为未定义。

【讨论】:

    【解决方案3】:

    *(unsigned long *)&amp;fValue 不等同于直接转换为 unsigned long

    转换为(unsigned long)fValuefValue 的值转换为unsigned long,使用将float 值转换为unsigned long 值的常规规则。 unsigned long 中该值的表示(例如,以位为单位)可能与 float 中表示相同值的方式完全不同。

    转换*(unsigned long *)&amp;fValue 正式具有未定义的行为。它将fValue 占用的内存解释为unsigned long。实际上(即这是经常发生的情况,即使行为未定义)这通常会产生与 fValue 完全不同的值。

    【讨论】:

      【解决方案4】:

      C 中的类型转换同时进行类型转换和值转换。浮点 → unsigned long 转换会截断浮点数的小数部分,并将值限制在 unsigned long 的可能范围内。从一种类型的指针转​​换为另一种类型的指针不需要更改值,因此使用指针类型转换是一种在更改与该表示关联的类型的同时保持相同的 in-memory 表示的方法。

      在这种情况下,这是一种能够输出浮点值的二进制表示的方法。

      【讨论】:

      • “从一种类型的指针转​​换为另一种类型的指针不需要更改值”,值表示可能会发生变化,也可能不会发生变化。在现代 PC 等上通常没有。但这无论如何都与 OP 的问题无关,我想你的意思是说,转换指针类型不会改变指针指向的内存内容。
      【解决方案5】:

      正如其他人已经指出的,将指向非字符类型的指针转​​换为指向不同非字符类型的指针,然后取消引用是未定义的行为。

      printf("%08lx\n", *(unsigned long *)&amp;fValue) 调用未定义的行为并不一定意味着运行一个试图执行此类讽刺的程序会导致硬盘驱动器擦除或使鼻恶魔从鼻子中喷出(未定义行为的两个标志)。在sizeof(unsigned long)==sizeof(float) 和两种类型具有相同对齐要求的计算机上,printf 几乎肯定会做人们期望它做的事情,即打印所讨论的浮点值的十六进制表示。

      这不足为奇。 C 标准公开邀请实现来扩展语言。严格来说,这些扩展中的许多都属于未定义行为的领域。例如,POSIX 函数dlsym 返回一个void*,但该函数通常用于查找函数的地址而不是全局变量。这意味着 dlsym 返回的 void 指针需要转换为函数指针,然后取消引用以调用函数。这显然是未定义的行为,但它仍然适用于任何符合 POSIX 的平台。这不适用于哈佛架构机器,在该机器上,函数指针的大小与数据指针的大小不同。

      类似地,将指向float 的指针转换为指向无符号整数的指针,然后取消引用恰好适用于几乎任何具有几乎任何编译器的计算机上,其中该无符号整数的大小和对齐要求与float.

      也就是说,使用unsigned long 很可能会给您带来麻烦。在我的计算机上,unsigned long 的长度为 64 位,并且具有 64 位对齐要求。这与浮点数不兼容。最好在我的电脑上使用uint32_t


      union hack 是解决这个烂摊子的一种方法:

      typedef struct {
          float fval;
          uint32_t ival;
      } float_uint32_t;
      

      分配给 float_uint32_t.fval 并从 ``float_uint32_t.ival` 访问过去是未定义的行为。在 C 中不再是这种情况。据我所知,没有编译器会为联合黑客攻击鼻恶魔。这不是 C++ 中的 UB。这是非法的。在 C++11 之前,兼容的 C++ 编译器必须抱怨才能兼容。


      任何更好的解决这个问题的方法是使用 %a 格式,它自 1999 年以来一直是 C 标准的一部分:

      printf ("%a\n", fValue);
      

      这很简单、容易、可移植,并且不会出现未定义的行为。这将打印所讨论的双精度浮点值的十六进制/二进制表示。由于printf 是一个古老的函数,所有float 参数在调用printf 之前都转换为double。此转换必须与 1999 版 C 标准完全一致。可以通过调用 scanf 或其姐妹来获取确切的值。

      【讨论】:

      • 感谢您添加此答案,它有助于进一步澄清事情!干杯。
      • 对于联合黑客来说,“C 中不再是这种情况”,我很确定你错了。 C11 6.5/7 规定了可以通过左值访问存储值的情况,并且由于floatuint32_t 不兼容,因此不允许这样做。仅访问具有上一个商店中使用的相同字段的联合的规则仍然有效。除非您有更高版本的标准来改变这一点,但是,由于 C17 只是由于 ISO 规则在需要新标准之前限制附录而创建的,我怀疑他们做出了如此根本的改变。
      猜你喜欢
      • 1970-01-01
      • 2020-12-22
      • 2013-04-01
      • 2011-11-24
      • 2011-02-24
      • 2013-04-29
      • 1970-01-01
      • 1970-01-01
      • 2011-08-20
      相关资源
      最近更新 更多