Keil c51号称作为51系列单片机最好的开发环境,大家一定都很熟悉。它的一些普通的特性大家也都了解,(书上也都说有)如:因为51内的RAM很小,C51的函数并不通过堆栈传递参数(重入函数除外),局部变量也不存储在堆栈中,而是存在于固定的RAM中及寄存器中。那么看一下下面的程序。
void fun1(unsigned char i)
{
}
正常情况参数i通过R7传入函数,那么它的实际地址在什么地方呢?就是R7吗?回答这个问题之前我们先来了解keil c51的几个有趣的特性(不考虑重入函数)。
一、函数在调用前定义与在调用后定义产生的代码是有很大差别的(特别是在优化级别大于3级时)。(本人也不太清楚为什么,大概因为在调用前定义则调用函数已经知道被调用函数对寄存器的使用情况,则可对函数本身进行优化;而在调用后进行定义则函数不知被调用函数对寄存器的使用情况,它默认被调用函数对寄存器(ACC、 B、 DPH、 DPL、 PSW、 R0、 R1、 R2、 R3、R 4、 R5、, R6、 R7)都已经改变,因此不在这些寄存器中存入有效的数据)
二、函数调用函数时除在堆栈中存入返回地址之外,不在堆栈中保存其它任何寄存器(ACC、 B、 DPH、 DPL、 PSW、 R0、 R1、 R2、 R3、R 4、 R5、, R6、 R7)的内容。(除非被调用函数使用了using特性)
三、中断函数是一个例外,它会计算自身及它所调用的函数对寄存器(ACC、 B、 DPH、 DPL、 PSW、 R0、 R1、 R2、 R3、R 4、 R5、, R6、 R7)的改变,并保存相应它认为被改变了的寄存器。
四、使用C写程序时,尽量少使用using n (n=0,1,2,3)特性。(这个特性在本人使用的过程中存在一些问题,不知算不算是一个小bug)
以下的试验都是在(环境 keil c51 v7.20)中,优化级为default下完成。
先看第一个特性问题。
例1:
void fun2(void)
{
}
void fun1(unsigned char i)
{
fun2();
while(i--);
}
它的汇编代码如下:
; void fun2(void)
RSEG ?PR?fun2?TEST
fun2:
; SOURCE LINE # 12
; {
; SOURCE LINE # 13
; }
; SOURCE LINE # 14
RET
; END OF fun2
;
; void fun1(unsigned char i)
RSEG ?PR?_fun1?TEST
_fun1:
USING 0
; SOURCE LINE # 16
;---- Variable ‘i?240’ assigned to Register ‘R7’ ----
; {
; SOURCE LINE # 17
; fun2();
; SOURCE LINE # 18
LCALL fun2
?C0003:
; while(i--);
; SOURCE LINE # 19
MOV R6,AR7
DEC R7
MOV A,R6
JNZ ?C0003
; }
; SOURCE LINE # 20
?C0005:
RET
; END OF _fun1
从中可以看到fun2()在fun1()前先定义,fun1()知道fun2()对寄存器的使用情况,知道R7没有改变,而参数i存于R7中,即i既是R7。(;---- Variable ‘i?140’ assigned to Register ‘R7’ ----)
看另一情况
void fun2(void);
void fun1(unsigned char i)
{
fun2();
while(i--);
}
void fun2(void)
{
}
汇编代码如下:
; void fun1(unsigned char i)
RSEG ?PR?_fun1?TEST
_fun1:
USING 0
; SOURCE LINE # 14
MOV i?140,R7
; {
; SOURCE LINE # 15
; fun2();
; SOURCE LINE # 16
LCALL fun2
?C0002:
; while(i--);
; SOURCE LINE # 17
MOV R7,i?140
DEC i?140
MOV A,R7
JNZ ?C0002
; }
; SOURCE LINE # 18
?C0004:
RET
; END OF _fun1
;
; void fun2(void)
RSEG ?PR?fun2?TEST
fun2:
; SOURCE LINE # 20
; {
; SOURCE LINE # 21
; }
; SOURCE LINE # 22
RET
; END OF fun2
fun2()在fun1()调用后定义,因fun1()调用fun2()时不知道fun2()对寄存器的使用情况,则认为fun2()改变了所有的寄存器(ACC、 B、 DPH、 DPL、 PSW、 R0、 R1、 R2、 R3、R 4、 R5、, R6、 R7)。因为fun1()认为fun2()改变了寄存器的值(包括R7),因此i虽然通过R7传递,但因已因调用fun2()而改变,所以不能再存在R7了,而上在RAM中额外的用一个Byte来存储。
这也就解释了在开始时的那个问题,参数i的存储是看问题而定的。
哈哈,是否很有趣呢。在节约RAM方面,这可是一个很有用的特性哦。(大家是否也为自己的节省了1Byte的RAM)
这个例子还解释了第二个特性,函数调用函数时除在堆栈中存入返回地址之外,不在堆栈中保存其它任何寄存器(ACC、 B、 DPH、 DPL、 PSW、 R0、 R1、 R2、 R3、R 4、 R5、R6、R7)的内容。函数在调用函数前,尽量不在这些寄存器中保存有效的数据,实在无法避免,则把有效数据存入固定的RAM中。
对于中断函数问题,当你看到下面的程序相差55 Byte时,不知你会怎么想的。
例2:
void OSTimeDly(void); //using 1
static void Timer0OVInt(void) interrupt 1 //using 1
{
TR0 = 0;
TH0 = 100;
TL0 = 100;
TR0 = 1;
OSTimeDly();
}
void OSTimeDly(void) //using 1
{
}
与
void OSTimeDly(void) //using 1
{
}
static void Timer0OVInt(void) interrupt 1 //using 1
{
TR0 = 0;
TH0 = 100;
TL0 = 100;
TR0 = 1;
OSTimeDly();
}
它们的汇编代码分别是,
; static void Timer0OVInt(void) interrupt 1 //using 1
RSEG ?PR?Timer0OVInt?TEST
USING 0
Timer0OVInt:
PUSH ACC
PUSH B
PUSH DPH
PUSH DPL
PUSH PSW
MOV PSW,#00H
PUSH AR0
PUSH AR1
PUSH AR2
PUSH AR3