考虑以下步骤:
-
numI 和 numF 是 2 个对象,它们恰好在您的架构上具有相同的大小,即 4 个字节。
-
&numI 是一个类型为int * 的表达式,其值为numI 对象的地址。
- 用
(float *)&numI 转换它是具有相同值的float * 类型的表达式。告诉编译器,这个地址是float的地址。
- 取消引用此表达式
*(float *)&numI 会生成float 类型的值,该值取决于int 对值3 的实际表示。例如,在英特尔核心处理器(小端、32 位、2s 补码)上,地址intI 处的字节将包含03 00 00 00。 float 对象使用 IEEE-756 标准在同一处理器的内存中表示:03 00 00 00 表示 非常 小值 1.5 x 2-148,大约 4.2039e-45。
- 将此值作为变量参数传递给
printf首先将其转换为double类型,具有相同的值,printf将值转换为0.000000,因为格式%f只指定了6个小数位并且没有指数。为了获得更精确的转换,您可以使用%g,它会产生4.2039e-45,或%a,它会产生十六进制表示0x1.8p-148。
- 程序的第二部分执行相反的转换:将 IEEE-756 表示为
00 00 40 40 的 float 值 00 00 40 40 重新解释为 int,生成值 1077936128。
这是您的程序的修改版本,使其更加明确:
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
int main(void) {
int numI;
float numF;
unsigned char *p;
assert(sizeof numI == sizeof numF);
numI = 3;
p = (unsigned char *)&numI;
printf("int value %d is represented in memory as %02X %02X %02X %02X\n",
numI, p[0], p[1], p[2], p[3]);
//numF = *(float *)&numI;
memcpy(&numF, &numI, sizeof numF);
printf("reinterpreted as float with format %%f: %f\n", numF);
printf("reinterpreted as float with format %%g: %g\n", numF);
printf("reinterpreted as float with format %%a: %a\n", numF);
printf("numF exact value: %g * 2^-148\n", numF * pow(2.0, 148));
numF = 3.0;
p = (unsigned char *)&numF;
printf("float value %.1g is represented in memory as %02X %02X %02X %02X\n",
numF, p[0], p[1], p[2], p[3]);
//numI = *(int *)&numF;
memcpy(&numI, &numF, sizeof numI);
printf("reinterpreted as int with format %%d: %d\n", numI);
printf("reinterpreted as int with format %%#X: %#X\n", numI);
return 0;
}
输出:
int value 3 is represented in memory as 03 00 00 00
reinterpreted as float with format %f: 0.000000
reinterpreted as float with format %g: 4.2039e-45
reinterpreted as float with format %a: 0x1.8p-148
numF exact value: 1.5 * 2^-148
float value 3 is represented in memory as 00 00 40 40
reinterpreted as int with format %d: 1077936128
reinterpreted as int with format %#X: 0X40400000
但请注意:
- 这些转换违反了严格的别名规则,因此具有未定义的行为。
-
int 和 float 类型在内存中的表示是特定于体系结构的。一些系统以大端字节顺序表示int,一些具有填充位和陷阱值,一些老式系统甚至使用一个补码或符号+幅度表示。 float 的表示可能更加奇特,尽管大多数当前系统使用 IEEE-756,其字节顺序与整数相同。因此,您的程序再次出现未定义的行为,并且充其量会产生特定于实现的输出。
- 在内存中重新解释表示的推荐方法是复制带有
memcpy 的字节,如修改后的代码所示。现代编译器会为此生成非常高效的代码,将 memcpy 扩展为仅 2 条指令(如果不是更少的话)。