« 上一篇下一篇 »

发现VC8.0的一个BUG

前段日子在用VC8.0调试程序的过程中发现了它的一BUG, 今天有空决定把它写出来. 看看下面这段代码:
程序代码:[ 复制代码到剪贴板 ]
    int A = 255789;
    float B = (float)A/(float)100;
    float C = B * 100;
    printf("result: %f \n\n", C);

在VC6.0下不管是Debug方式编译,还是Release方式编译 执行结果都正确.  如图:

按此在新窗口打开图片

然而在VC8.0下不管是Debug方式编译,还是Release方式编译 执行结果都有点怪. 如图:

按此在新窗口打开图片

装了VS80sp1的补丁后问题依旧,  比较了一下发现问题就出现在第二条语句float B = (float)A/(float)100; 
看了看生成的汇编码, 下面是vc6 Debug编译的

程序代码:[ 复制代码到剪贴板 ]
__real@4@4005c800000000000000 dd 1.0e2

//int A = 255789;
mov     [ebp+A], 255789

//float B = (float)A/(float)100;
fild    [ebp+A]
fdiv    ds:__real@4@4005c800000000000000 //操作的数是DWORD型, 定义在上面
fst     [ebp+B]


下面是vc8 Debug编译的

程序代码:[ 复制代码到剪贴板 ]
__real@4059000000000000 dq 1.0e2 

//int A = 255789;
mov     [ebp+A], 255789

//float B = (float)A/(float)100;
fild    [ebp+A]
fdiv    ds:__real@4059000000000000  //操作的数是QWORD型, 定义在上面
fstp    [ebp+B]


   众所周知,  IA32 CPU的浮点运算是基于栈的,  浮点单元包括8个浮点寄存器,  每个浮点寄存器的位宽都是80位,  和普通寄存器不同的是, 它们被当成一个浅栈(shallow stack)来对待,  这些寄存器分别标识为st0,st1~~直到st7. 其中st0在栈顶. 当压入栈中的值超过8个时,  栈底的值就会消失. (具体请参看<<深入理解计算机系统>>). 浅显点来讲就是利用某些浮点指令把被操作的数PUSH到浮点栈中,等CPU运算完成后再利用某些浮点指令把结果从栈中POP出来.  扯远了, ,  继续看我们的问题, 比较上面2段汇编代码就会发现他们有2处不同:
   第一处就是fdiv指令, 在VC6中它操作的是DWORD型的数, 在VC8中它操作的是QWORD(QWORD大小为8个字节), 在VC中类型float代表单精度浮点数, 占用大小为4个字节也就是32位, double类型代表双精度浮点数, 占用大小为8个字节也就是64位. 通过我的测试发现在VC8中 (1)float B = (float)A/(float)100; (2)float B = (float)A/(double)100; 这2条语句用VC8编译后居然没有任何区别, 内存中100都是用64位的双精度浮点数表示的, 而同样的语句用VC6编译的话 那么语句(1)100就会用32位单精度浮点数表示, 而语句(2)就会用64位的双精度浮点数表示, 我认为这应该是VC8的一个BUG. 
   第二处不同就是 fst,fstp的区别, 先看看这2条指令的说明.
FST
 指令格式:FST  STReg/MemReal
 指令的功能:将协处理器堆栈栈顶的数据传送到目标操作数中。在进行数据传送时,系统自动根据控制寄存器中舍入控制位的设置把栈顶浮点数舍入成相应精度的数据。
FSTP
 指令格式:FSTP  STReg/MemReal
 该指令的功能与FST相类似,所不同的是:指令FST执行完后,不进行堆栈的弹出操作,即:堆栈不发生变化,而指令FSTP执行完后,则需要进行堆栈的弹出操作,堆栈将发生变化。

 经过我的测试, 这个地方的不同才是导致运算结果有区别的直接原因, 哪怕把前面那2段汇编代码改成一样,只保留FST和FSTP的不同, 执行后还是会产生不同的结果, 不知为何. 各位有时间也可以自己测试一下, 知道原因了一定要告诉我. 我这里有个浮点指令体系的说明, 是以前网上下的, 觉的写的也还浅显易懂就收集了, 这里把它做为附件也一并附上  

点击下载此文件