sedatefie

【技术分享】你知道C51循环语句中的小秘密吗

0
阅读(2352)

#include <stdint.h>

uint8_t while_inc(uint8_t   ch)

{

    uint8_t i = 0;

 

    do

    {

         ch ++;

    }while (++i < 8);

    return ch;

}

#include <stdint.h>

uint8_t  while_dec(uint8_t ch)

{

    uint8_t i = 0;/*不产生汇编代码*/

    i = 8;

    do

    {

       ch ++;

    } while (--i);

    return ch;

}

代码-1  用“++”判断的while代码段

代码-2  用“--”判断的while代码段

上面两段示例代码中,循环次数都一样,但是在有些平台中,时空效率却大不相同。以51架构单片机为例,我们来分析它编译出来的汇编代码。

             ; FUNCTION _while_inc

;---- Variable 'ch' assigned to Register 'R7' ---- /* R7表示形参ch */

;---- Variable 'i' assigned to Register 'R6' ----/*R6表示局部变量i*/

0000 E4                CLR     A        /* i = 0; */

0001 FE                MOV     R6,A

0002         ?C0003:

0002 0F                INC     R7       /* ch++ */

代码“while (++i < 8)”对应汇编语句

0003 0E                INC     R6

0004 EE                MOV    A, R6

0005 C3                CLR     C

0006 9408              SUBB   A, #08H

0008 40F8              JC      ?C0003

000A         ?C0004:

000A 22                RET     

代码-3  “代码-1”编译得到的汇编代码段(C51平台)

             ; FUNCTION _while_dec

;---- /* R7表示形参ch   R6表示局部变量i */

0000 E4                CLR     A         /* uint8_t i = 0 令人困惑?? */

0001 7E08              MOV     R6,#08H  /* i = 8; */

0003         ?C0003:

0003 0F                INC     R7       /* ch++ */

代码“while (--i)”对应汇编语句

0004 DEFD              DJNZ    R6,?C0003

0006         ?C0004:

0006 22                RET     

代码-4  “代码-2”编译得到的汇编代码段(C51平台)

从上述的汇编代码中,我们发现用“++”和“--”的两段代码,前者比后者的代码,在空间上多了4条指令共5个字节。时间上,由于这是一个循环语句,累计至少多花费了8次*4=32个指令周期,如果循环次数进一步加大,这一差距就不能忽视。

为何会有如此之多的差异?其本质原因是因为C51拥有一条“复合指令DJNZ”,其定义如下,意为:将“寄存器或者某个ram内的byte变量”的值减1,完事后判断不为0则跳转”。该指令融合了“算术运算 + 逻辑运算 + 跳转”三个功能。


 而对于M3平台,两段代码并没有带来明显的时空差异。

while_inc PROC

        MOVS     r1,#0

|L1.2|

        ADDS     r0,r0,#1

        ADDS     r1,r1,#1

        UXTB     r1,r1

        UXTB     r0,r0

        CMP      r1,#8

        BCC      |L1.2|

        BX       lr

        ENDP

while_dec PROC

        MOVS     r1,#8

|L1.2|

        ADDS     r0,r0,#1

        SUBS     r1,r1,#1

        UXTB     r0,r0

        ANDS     r1,r1,#0xff

        BNE      |L1.2|

        BX       lr

        ENDP

代码-5 “代码-1”的汇编代码段(M3平台)

代码-6 “代码-2”的汇编代码段(M3平台)

这两段汇编,前者之所以比后者多了一句“UXTB     r1,r1”,因为加法的值需要32位和8位的数值转换,归零的减法不需要。这又应那个原则:“零值”操作效率较高。

因此,一个软件工程师,需要对其操作平台的指令系统有足够的熟悉,对基本C语句的汇编转换了然于心,才能写出高质、高效的代码。老工程师的价值,也体现在此。

另外值得一提的是,在这个实验中我发现一个比较有意思的现象:在函数while_dec中变量声明兼初始化代码“uint8_t i = 0;”,紧随其后就是“i = 8”的覆盖赋值代码。而它在C51平台编译器上,却产生了一个冗余代码“CLR   A”,令人困惑。在M3平台的编译中,就没有这样的现象了。不管怎样,随着编译器越来越智能,变量声明时即刻初始化,是一个非常好的编程习惯,它不会对代码空间增加额外的负担。