C51 优化设计之循环语句C51 有三种循环语句即 while,do-while 和 for, 这三 种循环都可以用来处理同一问题,基本上三者可以相互替 换•但由于C51是针对51汇编语言的编译器,如果不注意 51 汇编指令的特点,不同的编程方式可能得到不同的程 序性能(执行速度和代码长度). 以计算1+2+3+...+9+10 为例, 下面做一对比.程序 1:unsigned char i;unsigned char sum; for(i=1,sum=0;i从以上三个不同程序可以看出, 其运算结果都是 0x37(55), 但最短代码为9, 最长代码为1 5, 最快速度为53 最慢速度为130, 可见三个程序的性能差异较大.如何编出占用空间小运行效率高的循环代码呢?在 C51 编译环境下要写出优秀的循环代码必须熟悉 51 汇编 语言的指令系统•观察程序2,循环控制指令使用了 DJNZ 循环转移指令, 该指令同时完成计数和循环判断两种操作, 而且只占用两个字节, 是 51 指令系统中最为高效的循环 指令,因此在设计循环程序时,应尽可能使C51将DJNZ用 于循环程序中•当然DJNZ指令的循环次数是确定的,主要用在有确定循环次数的情况DJNZ 指令的一个最大特点是递减计数 ,因此循环程序 必须采用递减方式才有可能编译出DJNZ指令,如以上程 序 2.DJNZ 指令的另一个特点是先减后判断,因此设计循 环程序也必须坚持先减后判断的原则, 否则得不到 DJNZ 指令, 如以上程序 3.如果将程序3改写为: unsigned char i=10;unsigned char sum=0;while(i){sum+=i;i--;}就可以得到与程序2相同的汇编代码•若i—后还有其它 操作, 比如改为:unsigned char i=10,j=0;unsigned char sum=0;while(i){sum+=i;i--;j++;}也得不到DJNZ汇编指令,也就是说,循环语句在执行过程 中,减1与判断必须是连续的,且减1在前,判断在后.对于 while循环,当将减1与判断合成一步时,应当采用 while(--i).按照以上所述,do-while循环同样可以汇编 出DJNZ指令,不再一一列举.但是当循环变量不是通过常数赋值语句完成,而是来 自于另一个变量时for和while语句无论采用何种控制 流程都不能产生DJNZ指令,因为这两种循环都是先判断 后执行的控制逻辑,而DJNZ的执行过程是先执行循环体 后进行循环判断•按照DJNZ的控制流程,只有do-while 语句符合这个条件,因此当循环次数不是常量而是变量时 就必须使用do-while循环语句了.综上所述,若要使用DJNZ指令提高程序效率,在设计 循环程序中应坚持以下三大原则:① 采用递减计数;② 先减后判断,减与判断连续进行;③ 循环次数为变量时,采用do-while循环.8051单片机有两条循环指令,即DJNZ Rn,rel和DJNZ direct,rel・对于基本型单片机而言,两者的执行时间都 是2个机器周期,但两者的指令长度不同,前者占用2个字 节,后者占用3个字节.循环程序还涉及到循环变量初始 化操作,对于前者使用MOV Rn,#XX,2字节1周期,对于后 者使用MOV direct,#XX,3字节2周期.以单层循环为例, 使用工作寄存器比直接地址节省2字节1周期.除此之外, 两者相比,更重要的性能差异在于后者需要再分配一个内 存单元.因为通常程序模块都使用工作寄存器作局部变量 将工作寄存器用作循环变量不会增加内存占用量.总之, 使用工作寄存器作循环计数器是设计循环程序应坚持的 一项重要原则.一般情况下,C51编译器将循环次数赋予工作寄存器,比如unsigned char i;for(i=100;i;i--){dosomething();}但是存在下述情况之一时,C51编译的结果往往令人不满意:① 函数dosomething是一个外部定义C语言函数;② 函数dosomething是一个具有C语言接口,内部用汇编 语言实现的,供C程序调用的外部函数.以上两种情况循环变量i都存放在内存单元中,即采用直 接寻址方式•对于局部变量i,C51编译器采用了直接地址 存储,其原因在于基于这种假设,即在无任何特殊处理的 情况下,C51默认外部函数占用所有工作寄存器,因此在 循环的外部,不能修改这些已被占用的寄存器,C51只能 将循环控制变量分配在内存地址单元中.但如果循环体语 句中仅使用少数几个或甚至根本不使用工作寄存器,编译 器仍按这种假设处理, 那么编译器就不能显现出它的高效 性了•幸运的是,C51提供了弥补这一缺陷的伪指令 REGUSE.REGUSE 伪指令用于告知编译器某函数或子程序 占用了哪些寄存器或特殊功能寄存器 SFR, 编译器根据函 数提供的寄存器占用信息就可能将循环变量分配到循环 体未占用的寄存器中,从而达到优化设计的目的.另外, 一项开关必须打开, 即 Global Register Coloring, 方法是勾选 Project - Options for Target - C51 - Global Register Coloring.在情况①中, 应在函数 dosomething 所在源程序文件中添加代码(假设函数占用A和B):#pragma asm$REGUSE dosomething(A,B)#pragma endasm 重新编译项目后,在汇编窗口中可以看到,循环变量已使 用了工作寄存器.在情况②中,由于是汇编程序,只需增添一行代码(也假设子程序占用A和B):$REGUSE dosomething(A,B)同样可以观察到循环变量改成了工作寄存器实现需要注意的是,这里所说的寄存器占用是指在函数或 子程序执行过程中可能或肯定对这些寄存器造成破坏,即 执行写操作,对于只读寄存器不应按占用处理.另外,参数 传递使用的工作寄存器不必指明.。