那是在学习浮点数的一个精度问题的时候遇到的事情。
本来想把单精度浮点数打印出来看看内存存放情况的,结果发现了如下问题:
float f = 1.0f;
printf("f = 0x%08X, 1.0 = 0x%08X", f, 1.0f);
用gcc编译好后连续运行了几遍发现结果很奇怪
f = 0xC35E8D28, 1.0 = 0xC35E8D38
f = 0xE028AEF8, 1.0 = 0xE028AF08
f = 0xF329C0B8, 1.0 = 0xF329C0C8
至于这个怎么解释我目前也说不清楚,下面我就说说我能搜到的信息和我的总结。
首先是用%08X打印的初衷是希望能够像其他32位数据结构一样打印出float的内存存储情况。
因为我以为float的内存存储情况就是下面写的情况
符号位 阶码 尾数 长度 float 1 8 23 32 double 1 11 52 64但是实际上当变量f在printf中被用于打印时根本不是这样的进栈的。
我在网上找到了一个同学做的如下实验
实验一,检查%f需要读取几个字节
printf( " %f,%d\n " , a, b, c);
0,5
结论:%f读取8个字节,即两个整型大小
实验二,检查%lf需要读取几个字节
printf( " %lf,%d\n " , a, b, c);
0,5
结论:%lf也读取8个字节(也许和机器位宽有关,我是32位的机器)
实验三,检查printf读取float类型数据
int b = 5 ;
printf( " %f,%d\n " , a, b);
0.0,5
结论:float类型只占4个字节的数据,但前面实验一已经证明%f会读8个字节,即double类型的宽度,所以,编译器在将float类型参数入栈的时候,事先转换成了double类型。
实验四,再次证明实验三的结论
int b = 5 ;
printf( " %d,%d,%d\n " , a, b);
0,0,5
结论:a在入栈的时候,占了8个字节。
这样也难怪用%08X打印会出现乱七八糟的结果。
并且我在搜索过程中也遇到了很多人回帖说用printf时不要出现自己都不能预料结果的用法。
下表列出了printf和scanf对于各种格式说明符可以接受的参数类型。
格式 | printf | scanf |
%c | int | char * |
%d, %i | int | int * |
%o, %u, %x | unsigned int | unsigned int * |
所以对于float变量来说,就不应该用%x来打印。
这里还要额外说下:printf使用了vsprintf使用va_arg来取值,vsprintf内部,%x格式就是*(unsigned long*)&f
那么对于float类型的变量如何打印其内存情况呢?
应该用*(int*)&f ; *(int*)(&f) ; *((int*)&f)
printf("1.0 = 0x%08X, 1.0 = (int)%d\n", *(int*)&f, *(int*)&f);
结果为:1.0 = 0x3F800000, 1.0 = (int)1065353216
这个结果也能按照IEEE754浮点数来解释了。
但是其中不同的是%x虽然也是*(unsigned int*)&f,但是f在入栈的时候是8字节入栈的,要防止这个出现必须要让f在外面被强转。
反正一句话,不要乱用,按照规则用,否则结果难以预料。这次先写这些,下次继续吧。