点击链接加入群【�g攻防�J�泰g】:
不知不觉已经写到第三篇了,我们先来回顾一下在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的高,以及为什么高。
好了,这篇暂时就到这里,下一篇我们开始讲数据类型了哦,很重要的哦,应该说超级重要。