
关于C语言中printf函数“输出歧视”的问题
问题描述
昨天晚上被问到一个问题,为什么在同一个printf
函数中两次输出一个double
型变量会得不到正确的结果。具体代码大致如下:
#include <stdio.h>
int main()
{
int a;
double b;
double result;
printf("Please input a: ");
scanf("%d", &a);
printf("Please input b: ");
scanf("%lf", &b);
printf("\n");
result = a * b;
printf("result_int: %d\nresult_double: %f\n", result, result);
return 0;
}
看主函数的第18行语句,其中result
作为一个double
型变量,被以整型和浮点型分别输出了一次,按照正常思路,输出应该是下面这样:
Please input a: 12
Please input b: 12.3
result_int: 858993460
result_double: 147.600000
即前一个按整型输出时,会按照double
型低32位的数据,输出一个不期望的整数。
但是第二次输出时,则会按照其正确的形式(64位8字节double
类型)输出一个预期中的浮点数:147.600000
。
结果却大出意料,实际输出是下面这样:
Please input a: 12
Please input b: 12.3
result_int: 858993460
result_double: 0.000000
探索问题原因
这样的结果让人很意外,第一时间就想到了是否是因为result本身出了问题,所以在原程序后加入了一句printf("\n%f\n", result);
来确认result
是否发生了意料之外的变化。
但是很显然,并不是result
出了问题,因为单独输出的result
依然是正常的147.600000
。
那么接下来,既然result
本身没有问题,就应当想到是否在输出上出了问题。比如很显然地,%d
格式是标准的Int
型输出格式,正常来讲应该是32位数据,而double
则是64位数据,是否是这里出了问题呢?
比如在同一句输出里面,对同一个变量多次不完整输出,导致了其地址的递进式输出。
当然,到此时,这只是我的一种猜测。
要验证这个想法,最简单的莫过于直接查看内存内容。
如图
转换为4字节整数显示结果为
与输出
result_lower_32 = 858993459
result_higher_32 = 1080193843
resule_now = 147.600000
相符。
到此,产生这一问题的原因已经弄清楚了,并且最终问题应该是出在printf
函数的定义中,看来还是C语言
基础不牢,今天开始有必要重新学习,认真研究其每个细节。
另一种研究方法
实际上一开始因为不太熟悉VS的调试操作,并没能很快地在调试界面找到方法查看具体变量的内存地址及其内容,原因一则是很久没有用C语言
了,二则当初学的时候也没有认真,所以验证这个问题的过程十分艰辛,
中间还自己写了个函数想要输出数据的二进制格式,但是这个函数针对整型时工作正常,换成double
型之后,由于没想到办法对double
型和unsigned long long
型直接进行位运算,所以迫不得已对double
型数据进行了强制类型转换,最终发现强制类型转换会导致数据变化,无法保持其原型,所以这一思路作废。就此略过不表。
后来想起来C语言中可以对数据直接按16进制格式输出,故设计了如下程序进行尝试。
#include <stdio.h>
int main()
{
//该系统中,double为64位8字节,int和long为32位4字节。
//且为小端系统,即低位数据存储在内存的低位地址中。
double b;
printf("Please input the values of b:\n");
scanf("%lf", &b);
printf("\n");
printf("These are two parts of b:\n");
printf("\tlower byte = \t%#x\n\thigher byte = \t%#x\n\n", b, b);
printf("This is the whole b:\n");
printf("\t%#llx\n", b);
return 0;
}
输出如下(输入为12.3)
Please input the values of b:
12.3
These are two parts of b:
lower byte = 0x9999999a
higher byte = 0x40289999
This is the whole b:
0x402899999999999a
问题结论
从上述两种研究方法的结果,我们可以明显看到,在同一个输出格式字符串中,倘若针对某一个待输出量的转换说明不足以对应其所有的数据位,则printf
函数仅仅按照转换说明对应的数据位数,从低到高,依次输出。
所以对于介于初学和中级之间的学习者而言,研究stdio.h头文件的源码函数很有必要的。