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)
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
責任編輯;zl
評論