我写了一些c代码来处理内存中的浮点值,但最终从使用"gcc(gcc(12.1.1 20220730";带有-std=c11选项。
我不知道它为什么会这样,我想知道发生了什么,如果我做错了什么,以及如果可能的话,我如何让它将浮点值打印为十六进制,而不首先将其转换为另一种类型
以下是不同运行所使用的代码和输出。
代码:
#include <stdio.h>
int main()
{
float f3 = 1.1;
float f4 = 1.0;
unsigned char *t1 = &f3;
unsigned *t2 = &f3;
printf("P1: %xn", t2[0]);
printf("P2: %x %x %x %xn", t1[0], t1[1], t1[2], t1[3]);
printf("P3: %pn", &f3);
printf("P4: %lx, %lx, %llxn", &f3, f3, f4);
printf("T1: %f, %f, %lx, %lxn", f3, f4, f4, f3);
printf("T2: %x, %lxn", f4, f3);
printf("T3: %x, %lxn", f3, f4);
return 0;
}
主要问题似乎是将浮动打印为十六进制:
printf("%xn", f3);
输出1:
P1: 3f8ccccd // as expected
P2: cd cc 8c 3f // as expected
P3: 0x7ffc667d4d40 // as expected
P4: 7ffc667d4d40, 3ff19999a0000000, 0 // pointer value as expected, but second and third isn't. Values stay the same after each run
T1: 1.100000, 1.000000, 5556db09b2a0, 0 // first two values as expected, second and third isn't, Values do not stay the same after each run
T2: db09b2a0, 0 // this value keeps changing after each run
T3: db09b2a0, 0 // same as above, but should be different? Also changes after each run.
输出2:
P1: 3f8ccccd
P2: cd cc 8c 3f
P3: 0x7ffef87ebb00
P4: 7ffef87ebb00, 3ff19999a0000000, 0
T1: 1.100000, 1.000000, 55fb6a1962a0, 0
T2: 6a1962a0, 0
T3: 6a1962a0, 0
输出3:
P1: 3f8ccccd
P2: cd cc 8c 3f
P3: 0x7ffdb2026640
P4: 7ffdb2026640, 3ff19999a0000000, 0
T1: 1.100000, 1.000000, 564dd210c2a0, 0
T2: d210c2a0, 0
T3: d210c2a0, 0
主要问题似乎是将浮点打印为十六进制:
printf("%xn", f3);
是,因为%x
格式说明符需要一个unsigned int
作为参数,但您传入的是正在升级为double
的float
。
使用错误的格式说明符会触发未定义的行为,在本例中表现为奇怪的输出。至于幕后发生的事情,浮点值通常使用浮点寄存器传递给函数,而整数值通常被推送到堆栈上。
这也是无效的:
printf("P1: %xn", t2[0]);
因为它会导致严格的混叠冲突。这基本上意味着,除非目标类型是char
或unsigned char
,否则不能像访问另一种类型那样访问一种类型的字节。
打印浮点类型的字节表示的正确方法是使unsigned char *
指向第一个字节,然后循环遍历字节并打印每个字节。
在所有情况下;"意外输出";,您传递的参数的类型与格式说明符不匹配。你在告诉printf()
期待一件事,却忽略了另一件事。这总是未定义的行为,推测特定输出是如何产生的在很大程度上是毫无意义的。此外,将float
传递给printf()
会将其提升为双精度,因此您尝试检查的表示将与原始float
变量的表示不同
此外,printf()
在运行时处理这些类型-编译器的类型检查在这里对您没有帮助,适用的是printf()
的规则,而不是编译器的规则-它是一个可变函数,就编译器而言,可以接受任何数量的任何类型的参数。也就是说,许多编译器会检查stdio格式说明符,因为它们是标准库中定义良好的一部分——您可能需要启用特定的警告或更高的警告级别才能在编译器中启用该检查。
通常,要检查表示浮点值的位,您需要获取浮点的地址,将该地址强制转换为相同宽度的整数指针,然后取消引用。
但是严格的混叠规则会使其(严格(未定义,因此虽然*(uint32_t*)&f1
很可能会产生预期的整数值,但您不能依赖它。
一个定义良好的解决方案是以unsigned char
的形式访问字节,或者将字节复制(例如使用memcpy()
(到相同大小的整数对象。
例如:
#include <stdio.h>
#include <inttypes.h>
#include <stdint.h>
#include <string.h>
int main()
{
float f1 = 1.0f ;
float f2 = 1.1f ;
uint32_t b1 = 0u ;
uint32_t b2 = 0u ;
memcpy( &b1, &f1, sizeof(b1) ) ;
memcpy( &b2, &f2, sizeof(b2) ) ;
printf( "f1: %f @%p = %"PRIx32" (%02x %02x %02x %02x)n",
f1, &f1, b1,
((unsigned char*)&f1)[0],
((unsigned char*)&f1)[1],
((unsigned char*)&f1)[2],
((unsigned char*)&f1)[3] ) ;
printf( "f2: %f @%p = %"PRIx32" (%02x %02x %02x %02x)n",
f2, &f2, b2,
((unsigned char*)&f2)[0],
((unsigned char*)&f2)[1],
((unsigned char*)&f2)[2],
((unsigned char*)&f2)[3] ) ;
return 0;
}
输出:
f1: 1.000000 @0x7fffe3e06668 = 3f800000 (00 00 80 3f)
f2: 1.100000 @0x7fffe3e0666c = 3f8ccccd (cd cc 8c 3f)
(显然地址会有所不同(。
你也可以通过联盟实现类型双关:
typedef union
{
float f ;
uint32_t u ;
} uFloatIntPun ;
然后你可以例如:
uFloatIntPun pun ;
pun.f = f1 ;
printf( "%"PRIx32"n", pun.u ) ;
当然,如果您只想检查特定变量的位置、内部表示和字节顺序,那么在符号调试器中观察它们要简单得多,也不容易出错。