为什么要将float类型的指针转换为long类型的指针,然后解引用呢?
Why cast a pointer to a float into a pointer to a long, then dereference?
我正在经历这个例子,它有一个函数输出十六进制位模式来表示任意浮点数。
void ExamineFloat(float fValue)
{
printf("%08lxn", *(unsigned long *)&fValue);
}
为什么取fValue的地址,转换为无符号长指针,然后解引用?所有这些工作不都等同于直接转换为unsigned long吗?
printf("%08lxn", (unsigned long)fValue);
我试过了,答案不一样,很困惑。
(unsigned long)fValue
根据"常用算术转换",将float
值转换为unsigned long
值。
*(unsigned long *)&fValue
这里的目的是获取存储fValue
的地址,假装在这个地址上没有float
而是unsigned long
,然后读取unsigned long
。目的是检查用于在内存中存储float
的位模式。
如所示,这会导致未定义的行为。 原因:不能通过指向与对象类型不"兼容"的类型的指针来访问对象。"兼容"类型例如( 解决方案:转换为 请注意(正如@CodesInChaos所指出的),上面将浮点值视为首先存储其最高位字节("大端")。如果您的系统对浮点值使用不同的字节顺序,则需要进行调整(或重新排列以上unsigned
) char
和所有其他类型,或者共享相同初始成员的结构(这里说的是C)。详细的(C11)列表见§6.5/7 N1570 (请注意,我对"compatible"的使用与参考文本不同-更广泛。)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;
}
unsigned long
的字节,这对您来说更实用)。
浮点值具有内存表示:例如,字节可以使用IEEE 754表示浮点值。
第一个表达式*(unsigned long *)&fValue
将把这些字节解释为unsigned long
值的表示。事实上,在C标准中,它会导致未定义的行为(根据所谓的"严格混叠规则")。在实践中,有一些问题,如端序,必须考虑。
第二个表达式(unsigned long)fValue
是符合C标准的。它有一个精确的含义:
C11 (n1570),§6.3.1.4实浮点和整型
当将实浮点型的有限值转换为
_Bool
以外的整数类型时,小数部分被丢弃(即该值被截断到零)。如果整型部分的值不能用整数类型表示,则行为未定义。
*(unsigned long *)&fValue
不等同于直接强制转换为unsigned long
。
转换为(unsigned long)fValue
将fValue
的值转换为unsigned long
,使用float
到unsigned long
的正常转换规则。该值在unsigned long
中的表示(例如,以位表示)可能与float
中的表示完全不同。
转换*(unsigned long *)&fValue
在形式上具有未定义行为。它将fValue
占用的内存解释为unsigned long
。实际上(即这是经常发生的事情,即使行为是未定义的),这通常会产生与fValue
完全不同的值。
C中的类型转换既进行类型转换又进行值转换。浮点数→Unsigned long转换将截断浮点数的小数部分,并将值限制在Unsigned long的可能范围内。从一种类型的指针转换为另一种类型的指针不需要改变值,因此使用指针类型转换是在改变与该表示相关联的类型的同时保持内存中相同的表示的一种方法。
在这种情况下,它是一种能够输出浮点值的二进制表示的方法。
正如其他人已经注意到的,将指向非char类型的指针强制转换为指向不同非char类型的指针,然后解引用是未定义的行为。
printf("%08lxn", *(unsigned long *)&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进行访问。竞争曾经是一种未定义的行为。在c语言中不再是这种情况了。据我所知,没有一个编译器会因为联合攻击而怒斥鼻魔。这不是c++中的UB。这是非法的。直到c++ 11,一个兼容的c++编译器必须抱怨它是兼容的。
解决这种混乱的更好方法是使用%a
格式,它自1999年以来一直是C标准的一部分:
printf ("%an", fValue);
这很简单,容易,可移植,并且没有未定义行为的机会。这将打印所讨论的双精度浮点值的十六进制/二进制表示。由于printf
是一个古老的函数,所以在调用printf
之前,所有float
参数都被转换为double
。根据1999版的C标准,这种转换必须是精确的。可以通过调用scanf
或它的姊妹函数来获取这个精确的值。
- C++中的双指针类型转换
- C++默认情况下,指针类型数组的元素是否保证初始化为 nullptr?
- 将类指针类型转换为键时出错
- 错误:表达式必须具有算术、无作用域枚举或带有运算符重载的指针类型
- C++在一个映射中存储不同的指针类型(并处理销毁)
- 指针类型类成员的动态强制转换的恒定性是什么?
- 我正在尝试将表的地址传递给要在另一个函数中使用的指针,但得到不兼容的指针类型
- 在将派生类指针类型转换为派生类指针后,从基类指针调用派生类函数
- 如何使用静态多态性在 int 和指针类型之间进行转换?
- STL 函数和函数类型与函数指针类型
- 如何调用指针类型的方法(禁用多态性)?
- 为什么新表达式可以正确生成指针类型,即使它应该返回 void*?
- 对于非常量指针类型的参数,未调用具有常量指针模板类型参数的功能
- 是否允许调用方对我的 Builder 类使用任何指针类型(包括智能指针)?
- OPENCL 警告:不兼容的指针类型将'float __global[16]'传递给类型为 '__global float4 的参数 *
- 专门用于"direct"函数类型(与函数指针类型相对)
- 指向成员的指针类型和模板
- 返回对常量结构(指针类型)成员的引用:明显的左值到右值转换
- 在C++17中,为什么类模板和函数模板的指针类型推导明显不一致
- 为什么允许将整型、枚举和指向成员的指针类型reinterpret_cast到自身?