keil c语言编程常见错误分析要点 下载本文

表示为xxxx,第一位都是1嘛,干嘛还要表示呀?可以将小数点前面的1省略,所以23bit的尾数部分,可以表示的精度却变成了24bit,道理就是在这里,那24bit能精确到小数点后几位呢,我们知道9的二进制表示为1001,所以4bit能精确十进制中的1位小数点,24bit就能使float能精确到小数点后6位,而对于指数部分,因为指数可正可负,8位的指数位能表示的指数范围就应该为:-127-128了,所以指数部分的存储采用移位存储,存储的数据为元数据+127,下面就看看8.25和120.5在内存中真正的存储方式。

首先看下8.25,用二进制的科学计数法表示为:1.0001*

按照上面的存储方式,符号位为:0,表示为正,指数位为:3+127=130 ,位数部分为,故8.25的存储方式如下图所示:

而单精度浮点数120.5的存储方式如下图所示:

那么如果给出内存中一段数据,并且告诉你是单精度存储的话,你如何知道该数据的十进制数值呢?其实就是对上面的反推过程,比如给出如下内存数据:

0100001011101101000000000000,首先我们现将该数据分段,0 10000 0101 110 1101 0000 0000 0000 0000,在内存中的存储就为下图所示:

根据我们的计算方式,可以计算出,这样一组数据表示为:1.1101101*=120.5 而双精度浮点数的存储和单精度的存储大同小异,不同的是指数部分和尾数部分的位数。所以这里不再详细的介绍双精度的存储方式了,只将120.5的最后存储方式图给出,大家可以仔细想想为何是这样子的

下面我就这个基础知识点来解决一个我们的一个疑惑,请看下面一段程序,注意观察输出结果 )); ));

float f = 2.2f;

double d = (double)f;

Console.WriteLine(d.ToString(\ f = 2.25f; d = (double)f;

Console.WriteLine(d.ToString(\

可能输出的结果让大家疑惑不解,单精度的2.2转换为双精度后,精确到小数点后13位后变为了2.2000000476837,而单精度的2.25转换为双精度后,变为了2.2500000000000,为何2.2在转换后的数值更改了而2.25却没有更改呢?很奇怪吧?其实通过上面关于两种存储结果的介绍,我们已经大概能找到答案。首先我们看看2.25的单精度存储方式,很简单 0 1000 0001 001 0000 0000 0000 0000 0000,而2.25的双精度表示为:0 100 0000 0001 0010 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000,这样2.25在进行强制转换的时候,数值是不会变的,而我们再看看2.2呢,2.2用科学计数法表示应该为:将十进制的小数转换为二进制的小数的方法为将小数*2,取整数部分,所以0.282=0.4,所以二进制小数第一位为0.4的整数部分0,0.4×2=0.8,第二位为0,0.8*2=1.6,第三位为1,0.6×2 = 1.2,第四位为1,0.2*2=0.4,第五位为0,这样永远也不可能乘到=1.0,得到的二进制是一个无限循环的排列 00110011001100110011... ,对于单精度数据来说,尾数只能表示24bit的精度,所以2.2的float存储为:

但是这样存储方式,换算成十进制的值,却不会是2.2的,应为十进制在转换为二进制的时候可能会不准确,如2.2,而double类型的数据也存在同样的问题,所以在浮点数表示中会产生些许的误差,在单精度转换为双精度的时候,也会存在误差的问题,对于能够用二进制表示的十进制数据,如2.25,这个误差就会不存在,所以会出现上面比较奇怪的输出结果 三、xdata idata pdata data

DATA: 固定指前面0x00-0x7f的128个RAM,可以用acc直接读写的,速度最快,生成的代码也最小。

IDATA:固定指前面0x00-0xff的256个RAM,其中前128和dATa的128完全相同,只是因为访问的方式不同。

IDATA是用类似C中的指针方式访问的。汇编中的语句为:mox ACC,@Rx.(不重要的补充:c中IDATA做指针式的访问效果很好)

XDATA: 外部扩展RAM,一般指外部0x0000-0xffff空间,用DPTR访问。

PDATA: 外部扩展RAM的低256个字节,地址出现在A0-A7的上时读写,用movx ACC,@Rx读写。这个比较特殊,而且C51好象有对此BUG, 建议少用。但也有他的优点,具体用法属于中级问题,这里不提。

STARTUP.A51 的作用,和汇编一样,在C中定义的那些变量和数组的初始化就在STARTUP.A51中进行,如果你在定义全局变量时带有数值,如unsigned char dATa xxx=\那STARTUP.A51中就会有相关的赋值。如果没有=100, STARTUP.A51就会把他清0。(STARTUP.A51==变量的初始化)。 这些初始化完毕后,还会设置SP指针。对非变量区域, 如堆栈区,将不会有赋值或清零动作。有人喜欢改STARTUP.A51为了满足自己一些想当然的爱好,这是不必要的,有可能错误的。比如掉电保护的时候想保存一些变量, 但改STARTUP.A51来实现是很笨的方法,实际只要利用非变量区域的特性,定义一个指针变量指向堆栈低部:0xff处就可实现。, 为什么还要去改? 可以这么说:任何时候都可以不需要改STARTUP.A51,如果你明白它的特性。

bit是在内部数据存储空间中 20H .. 2FH 区域中一个位的地址,这在DATA的20H以后以字节形式出现,可互相参照。另外加上 8051 可寻址 的SFR,但刚刚试过,只是00H--7FH起作用,也就是说当数据有变化时颜色变红,以后的从80H到--FFH就不是位寻址区了,是位寻址的特殊寄存 器,如涉及到了可位寻址的那11个当然会有反应。复位后,程序计数器PC的内容为0000H,内部RAM各单元的值不确定。各功能寄存器的复位值如下:堆栈指针SP的复位值为07H,累加器ACC、寄存器B的复位值为00H,数据指针DPTR的复位值为0000H,而p0、p1、p2、p3四个口的复位值为0FFH。其他SFR如PSW、TCON、TMOD、TL0、TH0、TL1、TH1的复位值也为00H。

wave中是低128字节和高128字节(0-7FH),低128字节是片内RAM区,高128字节(80-FFH)是SFR(特殊功能寄存器)bit则是位于低128字节的20H .. 2FH 区域,即data的20H .. 2FH 区域

code 是在 0000H .. 0FFFFH 之间的一个代码地址。 我用ORG 5000H

TAB: DB 22H,3BH,43H,66H,5H,6DH,88H后, CODE从5000H开始以后变成DB各位

DATA是 在 0 到 127 之间的一个数据存储器地址,或者加 128 .. 255 范围内的一个特殊功能寄存器(SFR)地址。两者访问的方式不同。实际上由于PSW的复位设置PSW.3=RS0和PSW.4=RS1皆为0,所以通用工 作寄存器区就是第0区,所以data的00--07H部分是与 REG栏中的R0--R7对应的。以后的则仅代表低128字节的内部RAM。

IDATA是 0 to 255 范围内的一个IDATA存储器地址, IDATA与DATA重合低128字节,有的地方只有DATA表示256字节的片内RAM,

XDATA 是 0- 65535 范围内的一个XDATA 存储器地址。

指针类型和存储区的关系详解 一、存储类型与存储区关系 data ---> 可寻址片内ram bdata ---> 可位寻址的片内ram

idata ---> 可寻址片内ram,允许访问全部内部ram pdata ---> 分页寻址片外ram (MOVX @R0) (256 BYTE/页) xdata ---> 可寻址片外ram (64k 地址范围FFFFH)

code ---> 程序存储区 (64k 地址范围),对应MOVC @DPTR

二、指针类型和存储区的关系

对变量进行声明时可以指定变量的存储类型如:

uchar data x和data uchar x相等价都是在内ram区分配一个字节的变量。同样对于指针变量的声明,因涉及到指针变量本身的存储位置和指针所指向的存储区位置不同而进行相应的存储区类型关键字的使用如: uchar xdata * data pstr

是指在内ram区分配一个指针变量(\号后的data关键字的作用),而且这个指针本身指向xdata区(\前xdata关键字的作用),可能初学C51时有点不好懂也不好记。没关系,我们马上就可以看到对应“*”前后不同的关键字的使用在编译时出现什么情况。

uchar xdata tmp[10]; //在外ram区开辟10个字节的内存空间,地址是外ram的0x0000-0x0009 .第1种情况: uchar data * data pstr; pstr=\

首先要提醒大家这样的代码是有bug的, 他不能通过这种方式正确的访问到tmp空间。 为什么?我们把编译后看到下面的汇编代码:

MOV 0x08,#tmp(0x00) ;0x08是指针pstr的存储地址

看到了吗!本来访问外ram需要2 byte来寻址64k空间,但因为使用data关键字(在\号前的那个),所以按KeilC编译环境来说就把他编译成指向内ram的指针变量了,这也是初学C51的朋友们不理解各个存储类型的关键字定义而造成的bug。特别是当工程中的默认的存储区类为large时,又把tmp[10] 声明为uchar tmp[10] 时,这样的bug是很隐秘的不容易被发现。 第2种情况:

uchar xdata * data pstr; pstr = tmp;

这种情况是没问题的,这样的使用方法是指在内ram分配一个指针变量(\号后的data关键字的作用),而且这个指针本身指向xdata区(\前xdata关键字的作用)。编译后的汇编代码如下。 MOV 0x08,#tmp(0x00) ;0x08和0x09是在内ram区分配的pstr指针变量地址空间