不知不觉已经写到第三篇了,我们先来回顾一下在02中主要分析了哪些知识点,我们介绍了程序的组成结构PE结构,在这里再扩展一下大家的知识面,在LINUX平台下,也有类似PE结构的文件格式,也是对COFF的扩展,叫ELF,有兴趣的可以研究一下。还介绍了我们的HELLO WORLD一运行起来,操作系统就会为它分配内存,映射地址空间,逻辑地址空间中主要是四个区域,栈(国人一般喜欢称其为堆栈)、堆、代码区、数据区。而且我们也分析出了VC6中这几个区域的大概范围,也算是让大家对程序有一种简单的认识吧。
今天我想了一下,我们主要讨论一下01中的问题,01中我们主要讨论了整数( [unsigned] (char ,short,int) ) 在内存中的表示形式,今天我们再来做一个练习,然后把方向指向浮点数,并分析浮点数在内存中的表示形式,以及为什么浮点数的表示会有精度损失问题,这些问题本身并不重要,重要的是理解过程。
复习:随便给定两个int型整数,51245412和-6231541 写出其内存表示形式,假设起始地址为 0X0012FF7C
1、计算51245412的二进制表示形式,不够32位的话,左边补0(你懂的),这一步可以用计算器哦
51245412
0000 0011 0000 1101 1111 0001 0110 0100
然后就是写出其对应的十六进制了
0000 0011 0000 1101 1111 0001 0110 0100
0 3 0 D F 1 6 4
我们可以得到 它的十六进制表示形式:03 0D F1 64,然后按照高高低低的原则写入地址中
内存地址
数值
0X0012FF7C
64
0X0012FF7D
F1
0X0012FF7E
0D
0X0012FF7F
03
看实际的效果
注意,我们没有按F9进行下断点,取而代之的是嵌入了一段汇编代码,int 3; 其实就是一个软中断,CPU执行到这里自己就会断下来,跟我们用F9是一个道理,写这个东西出来主要是为了让大家见多识广嘛,呵呵。我们可以看到图中黑色选中部分与我们自己计算的一模一样。
2、 -6231541 这个数不再是正数,而是一个负数,我们要先计算其对应正数的二进制形式,然后按位取反再加1,才能求得最终结果
6231541
0000 0000 0101 1111 0001 0101 1111 0101
按位取反
0000 0000 0101 1111 0001 0101 1111 0101
1111 1111 1010 0000 1110 1010 0000 1010
再加1结果是
1111 1111 1010 0000 1110 1010 0000 1011
写成十六进制
1111 1111 1010 0000 1110 1010 0000 1011
F F A 0 E A 0 B
FF A0 EA 0B 就是我们的结果了
内存地址
数值
0X0012FF7C
0B
0X0012FF7D
EA
0X0012FF7E
A0
0X0012FF7F
FF
看实际结果
这下我们就又复习了一次补码,下面我们来研究一下浮点数的表示吧。
其实浮点数也是可以表示二进制的,这是必须滴嘛。。哈哈,如0.5 表示成二进制是 0.1
小数点前的1表示2^n,如 11.01 左起第1个1表示 1*2^1 ,左起第2个1表示1*2^0,最右边的1表示1*2^(-2) ,其和就是2+1+0.25 = 3.25,这应该不是很难理解吧。现在举一个例子,我们有一个整数19.375,我们要将它转换二进制,先转左边的整数部分,16+2+1 = 19 ,所以是 10011,那么右边的怎么转呢,如果大家有基础的话,都知道除2和乘2方法吧,我们就不去介绍那些方法了,如果你数学学的好,0.375你应该知道是多少,对是3/8 化简它,3/8=1/8+2/8 =1/8+1/4=2^(-3 ) + 2^(-2) ,明白了吧,我举0.375是有我的用意的,,哈哈,你被忽悠了。所以这个完整的二进制表示 应该是 10011.011 ,如果你看不懂这里,那你得好好看看小学数学了哦。我们已经有了二进制了,那么我们第一步要做的事情就是将它转换成1.xxxx*2^y 形式,就像 15.62 = 1.562*10^1 ,15642.125 = 1.5642125*10^4 这样子哦,只不过用的是二进制形式啦,如上面的结果我们就可以将它换算成 1.0011011 * 2^4 , 这个结果其实还是 19.375,只不过是 带指数的二进制表示形式了。
现在我们已经有了等价于 19.375 的 另一种表示形式 1.0011011 * 2^4 ,而我们说浮点数的内存表示形式也是从这里开始的,浮点数表示采用的方式不是补码了,而是移码。规定
最高位 符号位,为1表示为负数,为0表示为正数
最高位右边的8位 指数位,左加右减
其它23位 数值位
上表描述的是float类型的数据的表示,符号位好确定,如上,我们的19.375是正数,所以浮号位为0,那么指数位是怎么确定的呢?它是以127为根基,我们在将二进制转成换1.xxxx*2^y 时 ,将小数点进行了移位,移动了y位,如果是左移就用127+y 就是指数位的值,如果是右移,则用127-y的值,就得到指数位,由于我们是进行了左移,所以指数位为127+4 = 131 ,二进制形式为 10000011 ,这样我们就确定了前九个位,最后23位表示数值就是我们小数点右边的位0011011,您会说,不够23位呀,这还不简单嘛,在右边补0呀,小数在右边补0 是不更改原数大小的嘛。。结合一下就是下表
符号位
指数位
数值位
0
10000011
0011011 0000 0000 0000 0000
好了,我们将它们连起来,组合成16进制数据,就像前面做整数的时候一样
0100 0001 1001 1011 0000 0000 0000 0000
4 1 9 B 0 0 0 0
整合一下数据: 41 9B 00 00 写入内存空间
内存地址
数值
0X0012FF7C
00
0X0012FF7D
00
0X0012FF7E
9B
0X0012FF7F
41
验证奇迹的时刻到了
很神奇吧
如果换成负数,大家能不能把它的值直接写出来呢,就根据上面的数值,其实很简单了吧,我们是不是只需要将最高位置1即可,那么原来是4的地方现在值应该为1100,也就是C了吧,再次验证奇迹
是不是很EASY呢。
那么如果上面的例子你已经理解了,对于double型数据,只有几个地方需要改动一下就行了,double占8B,64b,还是最高位1位,指数位不是8b了,而是11位,而且基数127也不是127( 2^(8-1) -1 )了,而是1023( 2^(11-1) -1 ),剩余52b为数值位。希望大家可以自己举几个例子多做做测试。
好了,基础介绍就介绍这么多,问题是我们还有一个问题没有解决,那就是青藏高原,哦 MY GOD,错了,那就是浮点数精度损失问题。
为什么会有损失呢?
1、如果一个浮点数的二进制表示为 1.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx * 2^y ,这里x的位数明显超过23,这个时候,数值位就会进行截断,只取前23位,后面的就被截断了,当然就会损失精度了,这种问题可以用double解决,但是如果x的位数超过52,double也无能为力。
2、浮点数本身就换算不出有穷的二进制数,我们的小数部分之所以取0.375,是因为它可以用0.011表示出来,但是事实上还有一些数据是无法准确表示出来的,举一个例子,0.2,如果大家进行乘2取整 运算,会发现 进入循环,也就无法得到准确表示,这也是造成损失的原因。
我们通过分析精度损失原因,应该可以知道,double的精度要比float的高,以及为什么高。
好了,这篇暂时就到这里,下一篇我们开始讲数据类型了哦,很重要的哦,应该说超级重要。