注:我使用的不是
VC6.0,而是
TC2.0,所以有一些地方可能会与你不同。
注:由于写的比较仓促,有可能有错,也有可能不好懂,将就一下吧。。。
=================浮点数相关知识=================(如果你认为你懂可以略过)
以下是有关浮点数的解释(如果你找我要资料的话我只能说是来自《深入理解计算机系统》一书)
【Part1.有关IEEE浮点数的规定】
所谓
浮点数,顾名思义,就是小数点会浮动的数(有别于
定点数)
浮点数是一种表示实数的方法,它将一个实数V解释为:
V=2^X * Y,其中X和Y都为
整数
现在的浮点数都是使用
IEEE浮点数表示法(IEEE浮点表示法准确来说是一种规范,是
人为规定的),
每一个IEEE浮点数表示为:符号域|指数域|小数域,或说是:s|e|m,这里s,e都是二进制
整数串,(0≤m<1)m是个二进制
纯小数串。
IEEE浮点数有三种:
单精度浮点数(float):
符号域1位,指数域8位,小数域23位,共32位,Bias=2^(8-1) -1=127
双精度浮点数(double):
符号域1位,指数域11位,小数域52位,共64位,Bias=2^(11-1) -1=1023
扩展精度浮点数(c语言似乎不支持):
符号域1位,指数域15位,整数域1位,小数域63位,共80位(它比较特殊,这里不讨论)
注:Bias,Biased的缩写,中文翻译:偏置
IEEE浮点数的值分三种情况:规格化值,非规格化值,特殊数值
规格化值(用于表示绝对值较大的数字):
当指数域
不全为0且不全为1时,该IEEE浮点数为规格化值。
此时E=e-Bias,M=1+m…,S=s
V=(-1)^S * M * 2^E
非规格化值(用于表示绝对值较小的数字如0):
当指数域
全为0时,该IEEE浮点数为非规格化值。
此时E=1-Bias,M=0+m…,S=s
V=(-1)^S * M * 2^E
特殊数值(用于表示正无穷、负无穷、以及"不是一个数"):
当指数域
全为1时,该IEEE浮点数为特殊数值。
若小数域全为0且s=0时V=+∞
若小数域全为0且s=1时V=-∞
若小数域不全为0时,V=NaN
(Not a Number,不是一个数),比如虚数就是用NaN表示
【Part2.例子】
结合例子,比较容易理解【Part1】
float小数:1|01111110|10000000000000000000000
符号域:1
指数域:01111110,规格化值
小数域:10000000000000000000000
s=1
e=1
m=0.10000000000000000000000(2)
S=s=1
E=e-Bias=126-127=-1
M=1+m=1+0.1(2)=1.1(2)=1.5
V=-1.5 * 2^-1=-0.75
float小数:0|00000000|11111111111111111111111
符号域:0
指数域:00000000,非规格化值
小数域:11111111111111111111111
s=0
e=0
m=0.11111111111111111111111(2)
S=s=1
E=1-Bias=1-127=-126
M=0+m=0+0.1(2)=0.1(2)=0.5
V=0.5 * 2^-126=2^-127
double小数:0|11111111111|000000……
符号域:0
指数域:11111111111,特殊的值
小数域:000000……
V=+∞
================引入===================
写个c语言的程序:
#include <stdio.h>
int main(){
float x=1;
double y=1;
printf("%u %u %u %u\n",(float) x);
printf("%u %u %u %u\n",(double) x);
printf("%u %u %u %u\n",(float) y);
printf("%u %u %u %u\n",(double) y);
return 0;
}
程序输出:
0 0 0 16368
0 0 0 16368
0 0 0 16368
0 0 0 16368
============反汇编代码分析=============
IDA反汇编:
seg000:01FA ; int __cdecl main(int argc,const char **argv,const char *envp)
seg000:01FA _main proc near ; CODE XREF: start+11Ap
seg000:01FA
seg000:01FA var_14 = qword ptr -14h
seg000:01FA var_C = dword ptr -0Ch
seg000:01FA var_8 = word ptr -8
seg000:01FA var_6 = word ptr -6
seg000:01FA var_4 = word ptr -4
seg000:01FA var_2 = word ptr -2
seg000:01FA argc = word ptr 4
seg000:01FA argv = dword ptr 6
seg000:01FA envp = dword ptr 0Ah
seg000:01FA
seg000:01FA push bp
seg000:01FB mov bp, sp
seg000:01FD sub sp, 0Ch
以上是变量定义,以及函数初始化。
注意一下
var_14=-14h,
sp=bp-0Ch,后面有用。
seg000:0200 mov dx, 3F80h ; <suspicious>
seg000:0203 xor ax, ax
seg000:0205 mov word ptr [bp+var_C+2], dx
seg000:0208 mov word ptr [bp+var_C], ax
这是赋值语句“x=1”,dword var_C其实就是x,赋值后
x=0|01111111|00000000000000000000000
指数域为01111111,为规格化值,
e=127,E=0,
m=00000000000000000000000,
M=1.00000000000000000000000,
s=S=0,
V=(-1)^S * M * E=1.0
seg000:020B mov [bp+var_2], 3FF0h ; <suspicious>
seg000:0210 mov [bp+var_4], 0
seg000:0215 mov [bp+var_6], 0
seg000:021A mov [bp+var_8], 0
这是赋值语句 “y=1”,qword var_2其实就是y,赋值后y=0|011111111111|000000000000000000000000000000000000000000000000000
指数域为011111111111,为规格化值,
e=1023,E=0,
m=000000000000000000000000000000000000000000000000000,
M=1.000000000000000000000000000000000000000000000000000,
s=S=0,
V=(-1)^S * M * E=1.0
seg000:021F fld [bp+var_C] ; (emulator call)
seg000:0223 sub sp, 8
seg000:0226 fstp [bp+var_14] ; (emulator call)
seg000:022A wait ; (emulator call)
seg000:022C mov ax, 194h ; <suspicious>
seg000:022F push ax ; format
seg000:0230 call _printf
seg000:0230
seg000:0233 add sp, 0Ah
这里先将float类型的var_C转换为double类型,再调用printf函数(后面的add是堆栈平衡)
其实此时
sp=bp-0Ch-8h=bp-14h,而bp+var_14=bp-14h,所以
[bp+var_14]=[sp]。
可以看出var_C转换后直接入栈了。
这就是第一个printf函数:printf("%u %u %u %u\n",(float) x);
注:有关浮点指令请另查资料,我也不是很懂。
seg000:0236 fld [bp+var_C] ; (emulator call)
seg000:023A sub sp, 8
seg000:023D fstp [bp+var_14] ; (emulator call)
seg000:0241 wait ; (emulator call)
seg000:0243 mov ax, 1A1h ; <suspicious>
seg000:0246 push ax ; format
seg000:0247 call _printf
seg000:0247
seg000:024A add sp, 0Ah
第二个printf,跟第一个的差别仅在于格式字符串的地址不同,从而印证了我的猜想:
float在printf时被隐式的转换成了double。
下面的反汇编代码分析可以略过。
seg000:024D fld qword ptr [bp+var_8] ; (emulator call)
seg000:0251 sub sp, 8
seg000:0254 fstp [bp+var_14] ; (emulator call)
seg000:0258 wait ; (emulator call)
seg000:025A mov ax, 1AEh ; <suspicious>
seg000:025D push ax ; format
seg000:025E call _printf
seg000:025E
seg000:0261 add sp, 0Ah
第三个printf,跟前两个相差无几,不过fld指令的参数不是x而是y。
seg000:0264 push [bp+var_2]
seg000:0267 push [bp+var_4]
seg000:026A push [bp+var_6]
seg000:026D push [bp+var_8]
seg000:0270 mov ax, 1BBh ; <suspicious>
seg000:0273 push ax ; format
seg000:0274 call _printf
seg000:0274
seg000:0277 add sp, 0Ah
第四个printf,不需要类型转换,直接push。
seg000:027A xor ax, ax
seg000:027C jmp short $+2
seg000:027E mov sp, bp
seg000:0280 pop bp
seg000:0281 retn
seg000:0281
seg000:0281 _main endp
重置bp,返回0于ax中。
至此代码分析完成。
===============分析第四个printf=================
下面分析第四个printf,以说明“0 0 0 16368”这样的输出时怎样产生的。
(首先请确保你已经对IEEE浮点数有所了解)
“double y=1;”,赋值后
y=0|011111111111|000000000000000000000000000000000000000000000000000=1.0
seg000:0264 push [bp+var_2]
seg000:0267 push [bp+var_4]
seg000:026A push [bp+var_6]
seg000:026D push [bp+var_8]
这里是将double类型的y入栈,但是我们也可以看做是将4个unsigned类型的整数入栈:
0011111111111000,0000000000000000,0000000000000000,0000000000000000
就是16368,0,0,0
然后printf就将他们输出:0 0 0 16368
===============总结==================
在你的程序中:printf("%d",c);
输出0的原因如下:
1.float类型被强制转换为double
2.转换后double类型的低16位为0
3."%d"只输出了double类型的低16位
=====================================
还有不明白,可以自己查资料,也可以加QQ1025266550问我,注明来自PEDIY谢谢。