在用keil C51开发SS881X/SS880X系列芯片的时候,对于一些全局变量的初始化,会有如下两种不同的声音:
第一种:在定义这个全局变量的时候,同时将该变量赋值,代码如下:
char tmp0 = 0x55; // 不好,不要这样做
char tmp1 = 0xAA;
void main(void)
{
while (1);
}
第二种:定义全局变量之后,去将该变量赋值,代码如下:
char tmp0;
char tmp1;
void main(void)
{
tmp0 = 0x55; // GOOD,建议这样做
tmp1 = 0xAA;
while (1);
}
那么这两种方法对一些全局变量初始化有什么异同呢?显而易见tmp0和tmp1最终都分别赋值成了0x55、0xAA,所以他们就真的没区别吗?接下来我们看一下这两种方式编译出来的代码大小,第一种编译出来152字节,第二种编译出来23字节。很明显,这两种方法的差别巨大。
接下来,我们需要刨根问底,首先从汇编下手:
先看一下方法2的反汇编,因为它的结果比较符合我们的常识。
C_STARTUP:
C:0x0000 020003 LJMP C:0003
C:0x0003 787F MOV R0,#0x7F
C:0x0005 E4 CLR A
C:0x0006 F6 MOV @R0,A
C:0x0007 D8FD DJNZ R0,C:0006
C:0x0009 758109 MOV SP(0x81),#tmp1(0x09)
C:0x000C 02000F LJMP main(C:000F)
main:
C:0x000F 750855 MOV tmp0(0x08),#0x55
C:0x0012 7509AA MOV tmp1(0x09),#PDRV0(0xAA)
C:0x0015 80FE SJMP C:0015
可以看到执行main之前,还有一些指令,并且这段代码段是以C_STARTUP命名的,C_STARTUP其实就是整个程序执行的入口,地址必然是0x0000。那么接下来有一段循环,从0x0003->0x0007这些指令其实是做一件事情,就是把RAM地址的00H~7FH这128个字节的RAM清零,并且用的是间接寻址的方法(SS880X/SS881X系列的前128字节的RAM既可以直接寻址也可以间接寻址),等到清零了这128字节RAM以后,初始化了一下栈指针寄存器,紧接着就跳转到main函数中了,
在main函数中,就是直接对已经分配好地址的tmp0和tmp1进行赋值,这里是直接将常数赋值到地址的MOV direct, #data指令,就这两条就直接把全局变量的初始化搞定。
那么再看一下方法1的反汇编,看一下他究竟干了点啥。
C_STARTUP:
C:0x0000 020003 LJMP C:0003
C:0x0003 787F MOV R0,#0x7F
C:0x0005 E4 CLR A
C:0x0006 F6 MOV @R0,A
C:0x0007 D8FD DJNZ R0,C:0006
C:0x0009 758109 MOV SP(0x81),#tmp1(0x09)
C:0x000C 02004A LJMP C_START(C:004A)
C:0x000F 020096 LJMP main(C:0096)
C:0x0012 E4 CLR A
C:0x0013 93 MOVC A,@A+DPTR
C:0x0014 A3 INC DPTR
C:0x0015 F8 MOV R0,A
C:0x0016 E4 CLR A
C:0x0017 93 MOVC A,@A+DPTR
C:0x0018 A3 INC DPTR
C:0x0019 4003 JC C:001E
C:0x001B F6 MOV @R0,A
C:0x001C 8001 SJMP C:001F
C:0x001E F2 MOVX @R0,A
C:0x001F 08 INC R0
C:0x0020 DFF4 DJNZ R7,C:0016
C:0x0022 8029 SJMP C:004D
C:0x0024 E4 CLR A
C:0x0025 93 MOVC A,@A+DPTR
C:0x0026 A3 INC DPTR
C:0x0027 F8 MOV R0,A
C:0x0028 5407 ANL A,#0x07
C:0x002A 240C ADD A,#0x0C
C:0x002C C8 XCH A,R0
C:0x002D C3 CLR C
C:0x002E 33 RLC A
C:0x002F C4 SWAP A
C:0x0030 540F ANL A,#0x0F
C:0x0032 4420 ORL A,#0x20
C:0x0034 C8 XCH A,R0
C:0x0035 83 MOVC A,@A+PC
C:0x0036 4004 JC C:003C
C:0x0038 F4 CPL A
C:0x0039 56 ANL A,@R0
C:0x003A 8001 SJMP C:003D
C:0x003C 46 ORL A,@R0
C:0x003D F6 MOV @R0,A
C:0x003E DFE4 DJNZ R7,C:0024
C:0x0040 800B SJMP C:004D
C:0x0042 0102 AJMP C:0002
C:0x0044 04 INC A
C:0x0045 08 INC R0
C:0x0046 102040 JBC 0x24.0,C:0089
C:0x0049 8090 SJMP C:FFDB
C:0x004B 00 NOP
C:0x004C 8FE4 MOV 0xE4,R7
C:0x004E 7E01 MOV R6,#0x01
C:0x0050 93 MOVC A,@A+DPTR
C:0x0051 60BC JZ C:000F
C:0x0053 A3 INC DPTR
C:0x0054 FF MOV R7,A
C:0x0055 543F ANL A,#0x3F
C:0x0057 30E509 JNB 0xE0.5,C:0063
C:0x005A 541F ANL A,#0x1F
C:0x005C FE MOV R6,A
C:0x005D E4 CLR A
C:0x005E 93 MOVC A,@A+DPTR
C:0x005F A3 INC DPTR
C:0x0060 6001 JZ C:0063
C:0x0062 0E INC R6
C:0x0063 CF XCH A,R7
C:0x0064 54C0 ANL A,#INTCON2(0xC0)
C:0x0066 25E0 ADD A,ACC(0xE0)
C:0x0068 60A8 JZ C:0012
C:0x006A 40B8 JC C:0024
C:0x006C E4 CLR A
C:0x006D 93 MOVC A,@A+DPTR
C:0x006E A3 INC DPTR
C:0x006F FA MOV R2,A
C:0x0070 E4 CLR A
C:0x0071 93 MOVC A,@A+DPTR
C:0x0072 A3 INC DPTR
C:0x0073 F8 MOV R0,A
C:0x0074 E4 CLR A
C:0x0075 93 MOVC A,@A+DPTR
C:0x0076 A3 INC DPTR
C:0x0077 C8 XCH A,R0
C:0x0078 C582 XCH A,DPL(0x82)
C:0x007A C8 XCH A,R0
C:0x007B CA XCH A,R2
C:0x007C C583 XCH A,DPH(0x83)
C:0x007E CA XCH A,R2
C:0x007F F0 MOVX @DPTR,A
C:0x0080 A3 INC DPTR
C:0x0081 C8 XCH A,R0
C:0x0082 C582 XCH A,DPL(0x82)
C:0x0084 C8 XCH A,R0
C:0x0085 CA XCH A,R2
C:0x0086 C583 XCH A,DPH(0x83)
C:0x0088 CA XCH A,R2
C:0x0089 DFE9 DJNZ R7,C:0074
C:0x008B DEE7 DJNZ R6,C:0074
C:0x008D 80BE SJMP C:004D
C:0x008F 0108 AJMP C:0008
C:0x0091 5501 ANL A,0x01
C:0x0093 09 INC R1
C:0x0094 AA00 MOV R2,0x00
4: void main(void)
C:0x0096 80FE SJMP main(C:0096)
好家伙,这么多。笔者也没有一条一条的去看他究竟是在干啥,只关注一下RAM的初始化在哪里,经过单步调试发现,初始化RAM的指令总结出来在如下几条:
C:0x0016 E4 CLR A
C:0x0017 93 MOVC A,@A+DPTR
C:0x0018 A3 INC DPTR
C:0x0019 4003 JC C:001E
C:0x001B F6 MOV @R0,A
重点在于MOVC A,@A+DPTR这里,看到这条指令,笔者就猜想:定义全局变量的同时,将该变量初始化的操作,在编译器眼里,其实是将需要写入到RAM的初始值,通过查表的形式,将代码区的某一段位置读出,再去写入到RAM中去。(可以类比成为读写XDATA)
那么有什么方法可以论证这一点呢,可以结合一下编译出来的BIN文件以及反汇编来看,我们想要做的是把RAM的08H写0x55,RAM的09H写0xAA,所以如下反汇编其实是没有意义的(并且也不会跑到),他的作用就是去做了一个表,然后放在代码区中的某段地址后,在跑代码的时候将这个表解出来,所以代码量增加了这么多。
C:0x008F 0108 AJMP C:0008
C:0x0091 5501 ANL A,0x01
C:0x0093 09 INC R1
C:0x0094 AA00 MOV R2,0x00
如果您有耐心读到这里,想必已经了解了keil C51在初始化全局变量时候的坑了吧,最后给一个结论:
定义全局变量之后,一定要在函数中(如初始化时)将该变量赋初值(初值为0的话就不需要赋值,系统启动时会默认清零),而不是在变量定义的时候赋值。
原创文章,转发请注明出处。昇生微电子,www.sinhmicro.com。