printf函数为什么会影响程序吗?

fangguanghui 2007-10-26 06:32:20
今天发现一个问题,printf函数会改变程序,程序如下:
#include "stdafx.h"
#include "stdlib.h"

// TODO: reference any additional headers you need in STDAFX.H
// and not in this file
int main()
{
char amt[]="1111.11";
char amt1[100];
float ss = atof(amt)*100;
printf("%f\n",ss);
sprintf(amt1, "%d", (int)ss);
printf("%s\n", amt1);
getchar();
return 0;
}

运行程序,结果为:
111111.0000
111111
去掉第一个printf后,运行程序,结果为:
111110
第一个printf影响到了第二个printf函数,这是为什么呢?一头污水!!
...全文
1243 12 打赏 收藏 转发到动态 举报
写回复
用AI写文章
12 条回复
切换为时间正序
请发表友善的回复…
发表回复
fangguanghui 2007-10-26
  • 打赏
  • 举报
回复
感谢各位大侠的精彩解答,但是很惭愧,不懂汇编,对于之中的具体原因还是不清楚。
不过还是解开了一个谜团,非常感谢大家。
回头去下载个补丁。
loops 2007-10-26
  • 打赏
  • 举报
回复
我以前就以为FPU只有一组80位的寄存器,幸亏zhangyanli刨根挖底的精神,我才去查了资料,才发现原来是8组80位的寄存器。
因此上面的帖子中凡是说到存入FPU的寄存器,均指压入FPU的由寄存器组成的栈
loops 2007-10-26
  • 打赏
  • 举报
回复
浮点寄存器:
浮点执行环境的寄存器主要是8个通用数据寄存器和几个专用寄存器,它们是状态寄存器、控制寄存器、标记寄存器等
8个浮点数据寄存器(FPU Data Register),编号FPR0 ~ FPR7。每个浮点寄存器都是80位的,以扩展精度格式存储数据。当其他类型数据压入数据寄存器时,PFU自动转换成扩展精度;相反,数据寄存器的数据取出时,系统也会自动转换成要求的数据类型。
8个浮点数据寄存器组成首尾相接的堆栈,当前栈顶ST(0)指向的FPRx由状态寄存器中TOP字段指明。数据寄存器不采用随机存取,而是按照“后进先出”的堆栈原则工作,并且首尾循环。向数据寄存器传送(Load)数据时就是入栈,堆栈指针TOP先减1,再将数据压入栈顶寄存器;从数据寄存器取出(Store)数据时就是出栈,先将栈顶寄存器数据弹出,再修改堆栈指针使TOP加1。浮点寄存器栈还有首尾循环相连的特点。例如,若当前栈顶TOP=0(即ST(0) = PFR0),那么,入栈操作后就使TOP=7(即使ST(0) = PFR7),数据被压入PFR7。所以,浮点数据寄存器常常被称为浮点数据栈。
为了表明浮点数据寄存器中数据的性质,对应每个FPR寄存器,都有一个2位的标记(Tag)位,这8个标记tag0 ~ tag7组成一个16位的标记寄存器。


The FPU Instruction Set可以参见:
http://maven.smith.edu/~thiebaut/ArtOfAssembly/CH14/CH14-4.html
loops 2007-10-26
  • 打赏
  • 举报
回复
下面只讨论VC6 debug模式下面的程序。
我没贴全代码,从前面的代码,结合C++语句可知,ebp-70h 就是ss的地址,
fst dword ptr [ebp-70h] 是float ss = atof(amt)*100; 所生成的最后一条汇编,
就是把运算结果存入ss,也就是把FPU(浮点协处理器)寄存器中的内容存入ss,此时内存中的ss精度已有损失。
fst的指令说明可知,FPU的寄存器不发生变化,因此FPU的寄存器中依然存放的是精度未受损失的ss。

接下来,第一段程序开始调用printf("%f\n",ss);

printf("%f\n",ss);
004027D4 sub esp,8 //分配8个字节的空间,即double的空间,实质上相当于把参数push进堆栈
004027D7 fstp qword ptr [esp] //将ss存入新分配的8个字节的空间,完成给printf传入ss参数的任务
004027DA push offset string "%f\n" (00479078) //压入"%f\n"的地址,占用4个字节
004027DF call printf (00426ce0) //调用printf
004027E4 add esp,0Ch //回收空间,因为printf的参数共占用了12个字节。

接下来,再进行(int)ss,即把ss转换成int,需要调用__ftol函数。调用__ftol函数需要把参数压入FPU的寄存器中,由此

004027E7 fld dword ptr [ebp-70h] //从内存[ebp-70h]中取数至FPU的寄存器,此时[ebp-70h]已经是损失了精度的值了
004027EA call __ftol (00426e8c) //调用__ftol

而对于第二段程序,可以看到:

004027D1 fst dword ptr [ebp-70h] //将FPU的寄存器中取数存至内存ss
278: // printf("%f\n",ss);
279: sprintf(amt1, "%d", (int)ss);
004027D4 call __ftol (00426e7c) //直接调用__ftol,因为此时FPU寄存器中的数就是原始的最精确的ss,无需再从内存读入。


于是,这就产生了两种结果。
这是整个为什么不同的原因。

”将协处理器堆栈栈顶的数据传送到目标操作数中“是什么意思?
~~~~~~~~~~~
我估计就是把数据存入目标操作数所指的地址吧,我记得是FPU有8个8位的寄存器,这儿却叫做堆栈。最好去翻该指令的英文解释,这样更加准确。


zhangyanli 2007-10-26
  • 打赏
  • 举报
回复
to loops:

你的意思是从寄存器到内存的传输过程中损失的精度对吗?
而这又发生在fld dword ptr [ebp-88h] 这条指令上。
”将协处理器堆栈栈顶的数据传送到目标操作数中“是什么意思?
zhangyanli 2007-10-26
  • 打赏
  • 举报
回复
楼上的很厉害啊.只是楼主你在疯狂也够戗,能看懂多少啊 ,呵呵,我反正看的挺费劲的。

几条指令帮你看:
1、FLD
指令格式:FLD STReg(*)/MemReal
指令功能:将浮点数据压入协处理器的堆栈中。当进行内存单元内容压栈时,系统会自动决定传送数据的精度。比如:用DD或REAL4定义的内存单元数值是单精度数等。

(*) STReg是协处理器堆栈寄存器ST(0)~ST(7)。
例如:
.387
data1DD 123, -543
data2REAL8 -321.5
data3REAL10 2.5
……
FLDdata1;压一个单精度数据进栈
FLDdata2;压一个双精度数据进栈
FLDST(0);把堆栈寄存器ST(0)的值再压进栈
FLDdata3;压一个扩展精度数据进栈

2、FST 指令格式:FST STReg/MemReal
指令功能:将协处理器堆栈栈顶的数据传送到目标操作数中。在进行数据传送时,系统自动根据控制寄存器中舍入控制位的设置把栈顶浮点数舍入成相应精度的数据。

3、FSTP
指令格式:FSTP STReg/MemReal
指令功能:与FST相类似,所不同的是:指令FST执行完后,不进行堆栈的弹出操作,即:堆栈不发生变化,而指令FSTP执行完后,则需要进行堆栈的弹出操作,堆栈将发生变化。请见11.3.1节中的指令操作符命名规则的说明。
loops 2007-10-26
  • 打赏
  • 举报
回复
FPU的寄存器支持80bit的带宽,而double也不过是64bit的长度,由此,从寄存器到内存的转换将损失精度,这是lz的程序出问题的根源。
甚至,以前我写的用到大量的浮点运算程序的debug和release的结果都不一样。
cceczjxy 2007-10-26
  • 打赏
  • 举报
回复
估计是你的编译器优化的问题.
loops 2007-10-26
  • 打赏
  • 举报
回复
VC6 Debug下是这种情况,VS2005下就没问题了。他们的汇编是:

00448B0B fstp dword ptr [ebp-88h]
printf("%f\n",ss);
00448B11 fld dword ptr [ebp-88h]
00448B17 sub esp,8
00448B1A fstp qword ptr [esp]
00448B1D push offset string "%f\n" (4D02D4h)
00448B22 call @ILT+7435(_printf) (444D10h)
00448B27 add esp,0Ch
sprintf(amt1, "%d", (int)ss);
00448B2A fld dword ptr [ebp-88h] //从内存里取了数到FPU的寄存器中
00448B30 call @ILT+3480(__ftol2_sse) (443D9Dh)


00448B0B fstp dword ptr [ebp-88h]
// printf("%f\n",ss);
sprintf(amt1, "%d", (int)ss);
00448B11 fld dword ptr [ebp-88h] //从内存里取了数到FPU的寄存器中
00448B17 call @ILT+3485(__ftol2_sse) (443DA2h)
00448B1C push eax

两个程序在call ftol2之前,都再次从内存中取数到了FPU的寄存器,从而保证了结果的一致性。
星羽 2007-10-26
  • 打赏
  • 举报
回复
换编译器吧
coovi 2007-10-26
  • 打赏
  • 举报
回复
不知道你用的是哪个编译器,我用vs2003运行的结果是
运行程序,结果为:
111111.0000
111111
去掉第一个printf后,运行程序,结果为:
111111

没有出现你说的情况哈。
loops 2007-10-26
  • 打赏
  • 举报
回复

004027D1 fst dword ptr [ebp-70h] //将FPU的寄存器中取数存至内存
278: printf("%f\n",ss);
004027D4 sub esp,8
004027D7 fstp qword ptr [esp]
004027DA push offset string "%f\n" (00479078)
004027DF call printf (00426ce0)
004027E4 add esp,0Ch
279: sprintf(amt1, "%d", (int)ss);
004027E7 fld dword ptr [ebp-70h] //从内存[ebp-70h]中取数至FPU的寄存器
004027EA call __ftol (00426e8c) //调用ftol
004027EF push eax
004027F0 push offset string "%d" (00479074)
004027F5 lea eax,[ebp-6Ch]
004027F8 push eax
004027F9 call sprintf (00426be0)
004027FE add esp,0Ch


004027D1 fst dword ptr [ebp-70h] //将FPU的寄存器中取数存至内存
278: // printf("%f\n",ss);
279: sprintf(amt1, "%d", (int)ss);
004027D4 call __ftol (00426e7c) //调用ftol
004027D9 push eax
004027DA push offset string "%d" (00479074)
004027DF lea eax,[ebp-6Ch]
004027E2 push eax
004027E3 call sprintf (00426c50)
004027E8 add esp,0Ch

前一个加printf("%f\n",ss);的程序中,调用了printf("%f\n",ss);后,再次从内存中取数入FPU的寄存器,而后执行__ftol
而后一个,没有这个操作,直接调用__ftol运算,因此再次从内存取数入FPU的寄存器的过程,损失了精度。这是两者结果不一样的原因。

69,368

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧