格式化输入输出浮点数据的细微问题(C标准:printf,scanf)

2016-12-14 09:57:07来源:http://blog.csdn.net/gyymen/article/details/53534176作者:gyymen人点击

第七城市

刚开始学C语言的时候,看到用scanf输入浮点数据的对应字符串如下:float : %fdouble : %lfprintf输出的时候却都是统一的:float / double : %f你也许曾经跟我一样,用%lf输出过double,结果是正常的,因为%lf直接被当作了%f了。但是这时候问题就来了,问题1.C语言也算是强类型的语言,两种不同类型怎么能统一到同一种输出上呢?问题2.既然输出都可以统一,为什么输入必须用两种不同的格式串呢?实验一:确定以上结论int main(){ double d; scanf("%f",&d); printf("%f/n",d); scanf("%lf",&d); printf("%f/n",d); return 0 ;}两次都输入1,我电脑上的输出为:-92559604281615349000000000000000000000000000000000000000000000.0000001.000000可见用%f输入double型是不正确的,还有很多类似实验,就不一一列举了。输入两种格式*输出两种格式*两种类型变量 = 8 组 。实验二:规范的输入输出,以及汇编分析int main(){ float f; double d; scanf("%f",&f); scanf("%lf",&d); printf("%f/n",f); printf("%f/n",d); return 0 ;}这个实验的写法是符合标准的,两次输入1,能够输出正确的结果。现在要解决的就是提出的两个问题。我把汇编代码放在最后。这里挑关键说明一下。截取部分就是四个函数的调用,四块大致上是长这样:push ...push ...call ...add esp , ?(关于C调用约定,查一下就清楚了)第二个push的是格式字符串的地址,所以我们关心的在于第一个push,那里进去的是我们的变量第一个:lea eax, dword ptr [ebp-4]push eax先获取变量的有效地址,然后直接push第二个:lea ecx, dword ptr [ebp-C]push ecx除了地址外,其他和第一个一样。既然传给scanf 的都是变量的地址,我们当然就要用不同的格式字符串来区分了。第四个:先看第四个因为它更简单mov edx, dword ptr [ebp-8]push edxmov eax, dword ptr [ebp-C]push eaxebp-C就是我们d变量的地址,这里先让高双字[ebp - 8]入栈,然后再是低双字[ebp - C]。这些是由平台的字节顺序规定的,我的机器是小尾顺序,而esp的增长方向是向下的,所以这样 push进去的东西依然是小尾的。第三个:很猫腻的就在这里了fld dword ptr [ebp-4]sub esp, 8fstp qword ptr [esp][ebp - 4]是我们的变量f。第一条指令,f入浮点栈第二条指令,把esp减掉8,相当于在栈顶“腾"出了8字节的空间第三条指令,从浮点栈弹出一个数到指定内存。后面的esp由 qword修饰 , 即8字节。简要的说 ,就是利用机器本身的浮点指令完成了从float到double型的转换而我们传给printf的始终是一个double型。既然我们传给函数的都是同一种类型,当然没必要区分格式字符串了!至此,前面提出的两个问题算是有了一个说法。问题3:也许你也跟我一样,想到了既然传给scanf的都是一个地址,那我们给了一个double的地址,用了%f输入,但是机器不知道那是double的地址啊!一定会正确的输入一个浮点数的!就像下面这样 double d; scanf("%f",&d);那么输出呢?实验三:int main(){ double d; scanf("%f",&d); printf("%f/n",d); printf("%f/n",float(d)); printf("%f/n",*(float*)&d); return 0 ;}在我电脑上的输出为:-92559604281615349000000000000000000000000000000000000000000000.000000-92559604281615349000000000000000000000000000000000000000000000.0000001.000000第一个只是说明这样输出是错的。也许有人会说,既然正确输入了一个float,那么我来个强制转换吧。这就是第二个结果,其实也是错的。因为float(d)将产生double -> float 的类型转换。是按值转换,所以还是和上面一样的一个东西。那第三个呢?从结果看,显然是对的。这句话比较绕口,从右向左看:先取了d的地址,这里是double*的类型,然后指针类型转换,这里就是float*的类型了,最后脱指针引用,得到的就是一个float类型。呵呵,这个玩得稍微大了一点。只是想说明,虽然用scanf 和printf的格式串有细微的规定,但并不是一种八股,只要知道原理怎么来都行。有兴趣可以试下用%lf输入float,再输出也是可以的。不过注意,假设你输入输出用的是变量f,要这样:int main(){ float fspace; float f; 。。。试试把fspace去掉,即把float f放在main的第一行会出现什么!有意思吧。。。

实验二的汇编代码:========================

scanf("%f",&f);00412BA8 8D45 FC lea eax, dword ptr [ebp-4]00412BAB 50 push eax00412BAC 68 2C614200 push 0042612C ; ASCII "%f"00412BB1 E8 7AFFFFFF call scanf00412BB6 83C4 08 add esp, 8scanf("%lf",&d);00412BB9 8D4D F4 lea ecx, dword ptr [ebp-C]00412BBC 51 push ecx00412BBD 68 1C604200 push 0042601C ; ASCII "%lf"00412BC2 E8 69FFFFFF call scanf00412BC7 83C4 08 add esp, 8printf("%f/n",f);00412BCA D945 FC fld dword ptr [ebp-4]00412BCD 83EC 08 sub esp, 800412BD0 DD1C24 fstp qword ptr [esp]00412BD3 68 08704200 push 00427008 ; ASCII "%f",LF00412BD8 E8 93E4FEFF call printf00412BDD 83C4 0C add esp, 0Cprintf("%f/n",d);00412BE0 8B55 F8 mov edx, dword ptr [ebp-8]00412BE3 52 push edx00412BE4 8B45 F4 mov eax, dword ptr [ebp-C]00412BE7 50 push eax00412BE8 68 08704200 push 00427008 ; ASCII "%f",LF00412BED E8 7EE4FEFF call printf00412BF2 83C4 0C add esp, 0C===============================

第七城市

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台