刚才看到一位很牛的师兄写的一篇日志中提到了Keil C51using这个关键字的用法,粗心的我本来一直都没有留意它是用来干嘛的(因为我一般看见它都是在中断服务函数的定义开头处,好像没有了它也可以中断呀,所以才没怎么管),然而在日志中有看到这个关键字,所以也考究了一下,突然发现,原来这个东东和我最近在帮一个同学调的一个程序的时候突然遇到一个很怪的问题是有关系的,而且就是因为它才搞得程序莫明奇妙的出错(因为编译通过了,看起来也没什么错误,按C语言的逻辑分析也分析不出个什么所以然来,所以才怪)。

  后来调试了好久,甚至到http://www.51hei.com/keil%CF%C2%D4%D8.html 这里下载了好几个版本的keil,反汇编代码看了N次,基本各个版本出来的结果都差不多,不知情的情况下,推断出是Keil C51的编译器问题,以为Keil C51的编译器的变量空间分配出问题了,R7本来是用来作参数传递的,却也被编译器分配用来在主函数中被用来作循环变量,结果就是循环变量的汇编判断指令CJNE指令执行前,R7的值(也就是循环变量)被改变了,导致判断R7总是不能达到循环设置的值,就不断地在循环,也就致使按键扫描去抖动这个延时循环无法完成,就导致延时后的第二次判断无法进行,自然按键下来也就没反应了。后来尝试着关了定时器中断,又可以了,那就可以肯定是在执行CJNE前,R7的值被改变了,就是因为跳到了中断,然后中断函数执行的过程中,R7值被改变了(因为中断函数中还调用了其它的函数,而R7后来被分析反汇编代码时发现是被用作参数传递的),中断执行完后R7的值当然不正确啦!

  得出推断后我的解决办法就是:让C51编译好之后手动修改汇编的指令,不要直接使用寄存器来作循环变量,而是使用能实现间接寻地址的R1进行间接寻址(指向0x77)的方式,终于把问题解决了,因为0x77在执行中断函数的过程中没被修改,CJNE语句能正常判断了,循环能完成了,按键也能扫下来了,问题也可以解决了。后来我又想到用_at_关键字直接指定循环变量的分配地址,这样的话就不用手动改汇编指令了,编译器直接就是编译成使用间接寻址的,这也算解决了。但还是有个疑问,为什么编译器会不够聪明地把R7的空间分配错了呢?一直在疑问。。。。。。奇怪,怎么编译器会出这种问题?

直到我刚才考究过了using的用法之后,才发现原来自己的推断是错的,结论的总方向还是对的,只是问题并不在编译器身上,真正的问题根源如下:

1.我的同学写的那个程序,用到了定时器0,它的中断服务函数定义是这样写的:

{
    ........//一大串大代码,其中包括调用了其他函数,用到了R7作参数传递
}

这里就是加了个using 0,而问题就出在加了这个using 0,为什么?且听我的理解:

using关键字的作用就是指定某个函数在执行时切换寄存器组的:

51中有四个寄存器组,每个组有R0-R7这8个寄存器,用于CPU的数据处理,一般主函数main()默认使用第0组,因为PSW寄存器的初始值的第3、4位是00嘛,即默认指定了第0组。using 0就是指定在执行函数时切换为使用第0组,不加using关键字的话,一般都默认使用第0组,但这样的话在调用其他函数(包括中断服务函数)的时候,就会加入一些压栈指令以保护原来的R0-R7寄存器的(这样的话,程序执行会效率会低一点,因为会产生很多个指令是用来压栈出栈的),照这样说,只是加了using 0,使用的也是第0组又不是其它的组,程序就不应该有问题了吧,但是就是因为加了using 0,编译了一次,才发现在定时器中断函数t0()的入口中并没有发现把R0-R7的代码压入栈呀,就是说没有保护好R7呀,那当然就是在执行完之后回来R7不能回复原来的值啦,接下来的事情。。。。我就不说啦,这就是问题的根源,去掉using 0就可以了,编译器就自动帮你将R0-R7压栈,手动加了using 0,就是让编译器以为之前用的并不是第0组,而现在执行这个中断函数时就切换到第0组,而省去了将R0-R7这8个寄存器压入栈的指令了,这样虽然看起来是快了,然而对于这个程序来说却是致命的问题!!!因为根本没有保护好R0-R7,而没有保护R7并不是我预期发生的!!!这不是编译器的问题,是自己没有了解好、用好using的问题啊!还有,其实也可以在编译器选项里有选项来硬性规定编译器统一不直接使用寄存器而是使用间接寻址的办法来改变循环变量分配的地址的,或者使用

#pragma NOAREGS
;定义函数
#pragma AREGS

来规定某个函数的是这样子。

当然,这样的规定和使用using本身并不冲突,只是使用了这个关键字后就可能会间接地产生一系列的问题,让人郁闷了这么久,其实本来如果有详细地看整个程序的反汇编代码,或许当时就会发现发现少了那段压栈指令了,而不是说推断为编译器分配空间的问题了。。。。。。。。希望我的这次教训对各位有所帮助!!!

另外,using的用法,其实就是手动指定函数使用的寄存器组,用得不好,如果在中断里还有调用其它函数,用得不好会出现函数传递出错的,不信可以反汇编看看,建议如果对这个关键字用法和C51的结构及汇编不熟的话,请还是让C51编译器帮你好了,不要胡乱使用,因为会比较容易出错的,要切记哦!!!其实用using关键到底对在编译后会造成什么影响,建议自己亲自去查看汇编程序。

关于Keil C51中using关键字的使用心得的更多相关文章

  1. Keil C51中函数指针的使用

    函数指针在C语言中应用较为灵活.在单片机系统中,嵌入式操作系统.文件系统和网络协议栈等一些较为复杂的应用都大量地使用了函数指针.Keil公司推出的C51编译器是事实上80C51 C编程的工业标准,它针 ...

  2. KEIL C51中const和code的使用

    code是KEIL C51 扩展的关键字,用code修饰的变量将会被放到CODE区里.但C语里的const关键字好像也有定义不能改变的变量的功能,这两个关键字有什么区别呢?在帮助手册里查找const, ...

  3. KEIL C51 中嵌入汇编以及C51与A51间的相互调用

    如何在 KEIL C51(v6.21) 中调用汇编函数的一个示例 有关c51调用汇编的方法已经有很多帖子讲到,但是一般只讲要点,很少有对整个过程作详细描述,对于初学者是不够的,这里笔者通过一个简单例子 ...

  4. Keil C51中变量的使用

    引言 8051内核单片机是一种通用单片机,在国内占有较大的市场份额.在将C语言用于51内核单片机的研究方面,Keil公司做得最为成功.由于51内核单片机的存储结构的特殊性,Keil C51中变量的使用 ...

  5. Keil C51 中指针的使用

    指针是C语言中比较难的一个内容,Keil C51在指针方面有和标准C不一样的地方,今天看了一些资料学习了一下Keil C51 中指针的使用. keil51的指针,包含两种指针:普通指针,兼容标准C:内 ...

  6. C51中的关键字和ANSIC标准关键字

    C51中的关键字和ANSIC标准关键字 作       者:武力戡乱 修改日期:2017-09-05 备       注: 1.总备注信息 2.联系方式 3.其它博文链接:武力戡乱博客目录总表 内   ...

  7. [51单片机] Keil C51中变量的使用方法详解

    引言    8051内核单片机是一种通用单片机,在国内占有较大的市场份额.在将C语言用于51内核单片机的研究方面,Keil公司做得最为成功.由于51内核单片机的存储结构的特殊性,Keil C51中变量 ...

  8. Hash查找法在Keil C51中的实现

    摘要:散列(hash)是一种重要的存储方法,也是一种常见的查找方法.它是指在记录的存储位置和它的关键字之间建立一个确定的对应关系.本文以射频卡门禁控制器为例,说明用射频卡卡号作为关键字,用Hash查找 ...

  9. Keil C51 中的函数指针和再入函数

    函数指针是C语言中几个难点之一.由于8051的C编译器的独特要求,函数指针和再入函数有更多的挑战需要克服.主要由于函数变量的传递.典型的(绝大部分8051芯片)函数变量通过堆栈的入栈和出栈命令来传递. ...

随机推荐

  1. hdu 1535 Invitation Cards

    http://acm.hdu.edu.cn/showproblem.php?pid=1535 这道题两遍spfa,第一遍sfpa之后,重新建图,所有的边逆向建边,再一次spfa就可以了. #inclu ...

  2. DJANTO之FORM

    文档很仔细,但熟悉要慢慢来~~ from django.shortcuts import render from contact.forms import ContactForm from djang ...

  3. C++ crash 堆栈信息获取(三篇文章)

    最近在做程序异常时堆栈信息获取相关工作,上一篇文章成功的在程序creash时写下了dump文件,而有些情况写dump文件是 不可以的,比如在jni开发时,C++只做底层处理,而整个项目是android ...

  4. C# 知识点收集

    1. 数组复制 byte[] source; byte[] dest; int srcOffset = 0; int dstOffset = 0; int count = 10; System.Buf ...

  5. 线程篇-01-NSThread

    一.概述 1.使用NSThread创建线程的三种方式和区别. 二.核心 2.1 NSThread创建线程的三种方式和区别. 主要有NSThread对象的创建线程的类方法detachNewThreadS ...

  6. poj2262

                                                                                                   Goldb ...

  7. nyoj 234 吃土豆

    描述 Bean-eating * grid. Now you want to eat the beans and collect the qualities, but everyone must ob ...

  8. Animation Override Controller动画重载器

    假设游戏有很多个小人, 每一个人有2种动画站立,跑.  在通常情况下每一个人物都需要一个动画控制器. 有没有想过定义一个动画控制器 无须在定义全新的动画充值器实现每一个小人都播放自己的动画呢?没错An ...

  9. proxy set 拦截

    set方法用来拦截某个属性的赋值操作. 假定Person对象有一个age属性,该属性应该是一个不大于200的整数,那么可以使用Proxy保证age的属性值符合要求. let validator = { ...

  10. 关闭SQL Server 数据库所有使用连接

    使用存储过程终止:在查询分析器下创建终止数据库所有接连的存储过程,通过调用该存储过程可以关闭所有使用该数据库的连接操作.--创建终止使用数据库下所有进程的存储过程,参数为数据库名称use  maste ...