首页
社区
课程
招聘
一个C问题
发表于: 2010-7-13 21:05 4317

一个C问题

2010-7-13 21:05
4317
不明白C中的float型数据在内存中的二进制存在形式?指数部分和小数部分如何分布在32位中?最好以图表列出来,给点资料也行。
上面的问题没解决,以至于下面问题弄不清:
main()
{
        int a=7,b=8;
        float c=b/a;             /*b/a应该为1,所以C=1.000000;*/
        printf("%d\n",c);     /*将float型用printf转换为int型后变为0了为什么*/
        float m,n;
        m=9876123456.789e5;
        n=m+20;
        printf("%f\n%f\n",n,m);  /*为什么m,n的值相同,结果是怎么算出来的?*/
}
注释:本机编译环境是VC6.0+XPSP3,int型16位,float32位;
float型和int运算问题,虽然例子举的特殊点,但是想明白运算过程中内存的变化过程。

[课程]Linux pwn 探索篇!

收藏
免费 0
支持
分享
最新回复 (13)
雪    币: 93
活跃值: (55)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
2
===================浮点数相关知识==================
对于float,在内存中表示为:s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm
即1位s,8位e,23位m(请注意我区分了大小写)。

当eeeeeeee不为全1(e<>255)且不为全0(e<>0)时,小数为规格化值,否则为非规格化值。
规格化值:E=e-Bias,M=1.mmmmmmmmmmmmmmmmmmmmmmm,S=s
非规格化值:E=1-Bias,M=0.mmmmmmmmmmmmmmmmmmmmmmm,S=s
对于float,Bias=127

浮点小数的值为:V=(-1)^S * M * 2^E

===================问题1==================
(查书查到的)
b/a的二进制表示为:0 000000000000001

c的二进制表示为:0 00000000 00000000000000000000001
显然s=0,e=0,为非规格值,因此
S=0
E=-126
M=0.00000000000000000000001
V=2^-23 * 2^-126≈0
所以printf函数会输出0

===================问题2==================
(自己想的,大致说一下)
浮点数是有误差的,它只能精确到一定的有效数字。
虽然m,n的值看似不等,但储存在内存中时他们是相等的。
简单的说,就是产生误差了~
2010-7-13 23:52
0
雪    币: 934
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
phrain兄台,我已经知道第二个问题的原因了,正如你所说是误差,float的有效位只有6~7位,后面都是无效的,没有意义。
第一个问题中不明白S, E,M代表什么啊,为什么二进制C是那样表示啊,你能不能把你查到资料的给分我,给个地址也行,我去看看,然后再实践实践。
还有浮点小数的值为:V=(-1)^S * M * 2^E。这个公式是怎么得到的。
谢谢啦。
2010-7-14 10:08
0
雪    币: 287
活跃值: (102)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
4
不是计算的问题
编译器的问题,数据类型不同,printf的参数有差异,调试下就知道了

printf("%d\n",c); --------------printf("%d\n",(int)c);
2010-7-14 10:11
0
雪    币: 934
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
cxhcxh,用的是什么编译器,难道printf的不是0?
2010-7-14 10:16
0
雪    币: 287
活跃值: (102)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
6
VC6.0--------------
2010-7-14 10:17
0
雪    币: 934
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
printf("%d\n",c); --------------printf("%d\n",(int)c);
刚刚才看到你这句,这句好像不能说明问题啊,
和我早试过的这句没啥区别
printf("%d\n",c);-----------------改为printf("%f\n",c);
结果一样是正确的,根本不能体现float在内存里面的存在形式啊。
2010-7-14 10:20
0
雪    币: 287
活跃值: (102)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
8
[QUOTE=cxhcxh;835519]不是计算的问题
编译器的问题,数据类型不同,printf的参数有差异,调试下就知道了

printf("%d\n",c); --------------printf("%d\n",(int)c);...[/QUOTE]

不多说了.......
2010-7-14 10:24
0
雪    币: 934
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
先谢谢!回去调试下,公司电脑上面没装VC。
2010-7-14 10:30
0
雪    币: 93
活跃值: (55)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
10
问题一,似乎我弄错了,我再研究一下。。。
2010-7-14 12:59
0
雪    币: 401
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
http://baike.baidu.com/view/339796.htm
或者任何一本计算机组成原理教材上都会有这一章的内容。
2010-7-14 13:10
0
雪    币: 93
活跃值: (55)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
12
似乎跟printf函数有关系,我
printf("%d",1.1e5);
还是输出0
不知道跟你的编译器是否一致(无VC6,压力大)
在我的电脑上,
        printf("%u %u %u %u\n",(float) x);
        printf("%u %u %u %u\n",(double) x);
的输出保持一致,看上去float被强制转换成了double.
2010-7-14 13:12
0
雪    币: 93
活跃值: (55)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
13
注:我使用的不是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谢谢。
2010-7-14 17:49
0
雪    币: 934
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
真是长见识了,也通过这个讨论恶补了下计算机组成原理的相关知识,当时上大学的时候没认真学,真后悔啊
最后总结下收获:
1.对于float,在内存中表示为:s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm
其实按float占用4个字节(32bit)来分是这样的:
SEEE EEEE    EMMM MMMM    MMMM MMMM    MMMM MMMM     
注释: S: 表示浮点数正负,1为负数,0为正数
      E: 指数加上127后的值的二进制数
      M: 24-bit的底数(只存储23-bit)
其中0是特例,编译器会自动处理(phrain 也谈到了).
2.关于phrain谈到的规格化个人觉得说复杂了,总结简单一点就是小数点前面只有一位非零的数即为规格化表示。
3.关于float在内存中的表现形式和IEEE-754格式标准可以参考phrain上面介绍的(写的很认真,仔细),也可参看http://blog.sina.com.cn/s/blog_61c9bb670100fbwh.html这里面的一些实例应用。
4.内存中数值实际上是以二进制补码形式存在的,正数的补码是其本身,负数的补码是其绝对值的二进制按位取反后加1(组成原理知识).
5.关于phrain兄台数次强调的float在printf时被隐式的转换成了double这个我早就知道,在此说明下原因:不论是int 还是float型在进行混合运算时都先转换成double型进行计算然后再转换,原因和速率有关,而且这也是系统规定的。
6.最后就是printf输出的是double的低16位,所以结果为零,其实就是堆栈先进后出原则。

在此谢谢phrain如此的关注和详尽的解答,很受用。最后收藏此贴已备今后复习和速查
2010-7-15 17:21
0
游客
登录 | 注册 方可回帖
返回
//