引起我注意Keil c函数参数传递的是在一个偶然的机会,我在写一个函数的时候:如下
write_byte(ulong addr, uchar dat)
{
…
}
我原本以为addr会通过R4—R7来传递,而dat则通过R3传递的,在调试的时候却发现dat是通过固定地址储存区传递的。因此引发了我想知道它到底是怎样传递参数的。
因为大家都知道,keil c的参数传递规则
参数 char, int, long, generic
数目 1-byte pointer 2-byte pointer float pointer
1 R7 R6 & R7 R4-R7 R1-R3
2 R5 R4 & R5
3 R3 R2 & R3
我原本以为三个int参数都通过寄存器能传递,一个ulong和一个uchar应该也可以吧,真是有点不明白。如果写为如下
write_byte(ulong addr, uchar * dat)
{
…
}
即一个ulong参数和一个指针参数则都能通过寄存器传递。
导致上面第二个uchar参数不通过寄存器传递的原因大概是因为第二个uchar型参数一定要通过R5来传递吧,而第一个ulong型参数正好用了R5,所以dat就只能通过固定地址的存储区来传递了。
为此我就想测试一下如果不通过寄存器来传递参数或通过模拟栈来传递参数时参数的传递情况会是怎样的。
第一个例子如下,通过寄存器传递参数,代码共101字节。
void delay(ulong dlyh, uint dlyl)
{
while(dlyh--);
while(dlyl--);
}
void main(void)
{
delay(0x100, 0x50);
while(1);
}
汇编代码如下,
RSEG ?PR?_delay?TEST_PARA
_delay:
USING 0
; SOURCE LINE # 6
MOV dlyh?040+03H,R7 ;把从寄存器R4—R7传来的第一个参数
MOV dlyh?040+02H,R6 ;存入固定地址。
MOV dlyh?040+01H,R5 ;(有时真觉得这动作啥冒)
MOV dlyh?040,R4
; {
; SOURCE LINE # 7
?C0001:
; while(dlyh--);
; SOURCE LINE # 8
MOV R0,#LOW (dlyh?040)
MOV A,#0FFH
LCALL ?C?LLDIIDATA8
MOV A,R4
ORL A,R5
ORL A,R6
ORL A,R7
JNZ ?C0001
?C0003:
; while(dlyl--);
; SOURCE LINE # 9
MOV A,dlyl?041+01H
DEC dlyl?041+01H
MOV R6,dlyl?041
JNZ ?C0009
DEC dlyl?041
?C0009:
ORL A,R6
JNZ ?C0003
; }
; SOURCE LINE # 10
?C0005:
RET
; END OF _delay
;
; void main(void)
RSEG ?PR?main?TEST_PARA
main:
USING 0
; SOURCE LINE # 12
; {
; SOURCE LINE # 13
; delay(0x100, 0x50);
; SOURCE LINE # 14
MOV ?_delay?BYTE+04H,#00H ;传递第二个参数
MOV ?_delay?BYTE+05H,#050H
MOV R7,#00H ;传递第一个参数
MOV R6,#01H
MOV R5,#00H
MOV R4,#00H
LCALL _delay ;函数调用
?C0006:
; while(1);
; SOURCE LINE # 15
SJMP ?C0006
; END OF main
我们看到,第一个参数在调用函数里先送到R4—R7,然后在被调用函数delay里又从R4—R7传递到固定存储地址。
第二个例子仍然是第一个例子的函数,但是不通过寄存器传递参数,共94字节。
汇编代码如下:
RSEG ?PR?delay?TEST_PARA
delay:
USING 0
; SOURCE LINE # 6
; {
; SOURCE LINE # 7
?C0001:
; while(dlyh--);
; SOURCE LINE # 8
MOV R0,#LOW (dlyh?040)
MOV A,#0FFH
LCALL ?C?LLDIIDATA8 ;取参数
MOV A,R4
ORL A,R5
ORL A,R6
ORL A,R7
JNZ ?C0001
?C0003:
; while(dlyl--);
; SOURCE LINE # 9
MOV A,dlyl?041+01H
DEC dlyl?041+01H
MOV R6,dlyl?041
JNZ ?C0009
DEC dlyl?041
?C0009:
ORL A,R6
JNZ ?C0003
; }
; SOURCE LINE # 10
?C0005:
RET
; END OF delay
;
; void main(void)
RSEG ?PR?main?TEST_PARA
main:
USING 0
; SOURCE LINE # 12
; {
; SOURCE LINE # 13
; delay(0x100, 0x50);
; SOURCE LINE # 14
CLR A
MOV ?delay?BYTE+03H,A ;传递第一个参数
MOV ?delay?BYTE+02H,#01H
MOV ?delay?BYTE+01H,A
MOV ?delay?BYTE,A
MOV ?delay?BYTE+04H,A ;传递第二个参数
MOV ?delay?BYTE+05H,#050H
LCALL delay ;调用函数。
?C0006:
; while(1);
; SOURCE LINE # 15
SJMP ?C0006
; END OF main
我们可以看到,在这个例子中,调用函数直接把参数传递到固定的存储地址,少了通过寄存器这一步,产生的代码更小。
第三个例子,还是原来的函数,但是参数通过模拟栈传递,代码共128字节。
void delay(ulong dlyh, uint dlyl) reentrant
{
while(dlyh--);
while(dlyl--);
}
void main(void)
{
delay(0x100, 0x50);
while(1);
}
产生的汇编代码如下
RSEG ?PR?_?delay?TEST_PARA
_?delay:
USING 0
; SOURCE LINE # 6
MOV A,?C_IBP
ADD A,#0FCH ;模拟栈指针减四
MOV ?C_IBP,A
MOV R0,A
LCALL ?C?LSTIDATA ;保存第一个参数
?C0001:
; {
; while(dlyh--);
; SOURCE LINE # 8
MOV R0,?C_IBP
MOV A,#0FFH
LCALL ?C?LLDIIDATA8 ;取参数
MOV A,R4
ORL A,R5
ORL A,R6
ORL A,R7
JNZ ?C0001
?C0003:
; while(dlyl--);
; SOURCE LINE # 9
MOV A,?C_IBP
ADD A,#04H
MOV R0,A
INC R0
MOV A,@R0
DEC @R0
DEC R0
MOV AR6,@R0
JNZ ?C0009
DEC @R0
?C0009:
ORL A,R6
JNZ ?C0003
; }
; SOURCE LINE # 10
?C0005:
MOV A,?C_IBP
ADD A,#06H
MOV ?C_IBP,A
RET
; END OF _?delay
;
; void main(void)
RSEG ?PR?main?TEST_PARA
main:
USING 0
; SOURCE LINE # 12
; {
; SOURCE LINE # 13
; delay(0x100, 0x50);
; SOURCE LINE # 14
DEC ?C_IBP
DEC ?C_IBP
MOV R0,?C_IBP ;传递第二个参数
MOV @R0,#00H
INC R0
MOV @R0,#050H
CLR A
MOV R7,A ;第一个参数,通过寄存器传递。
MOV R6,#01H
MOV R5,A
MOV R4,A
LCALL _?delay
?C0006:
; while(1);
; SOURCE LINE # 15
SJMP ?C0006
; END OF main
第三个例子可以看出,通过模拟栈进行参数传递的函数,调用函数仍然尽可能的通过寄存器组传递参数,然后被调用函数再把寄存器传递来的参数保存入模拟栈。
这里只是举了几个例子来说明keil c的参数传递情况,也许举例的函数不是很一般,不能太说明问题,各位可自己试试。
现在我们来总结一下。(这里不考虑太简单的函数,特例是函数存参数的存储器在寄存器组里面,这方面的问题可去看我的另一辩文章“keil c的一些有趣特性”)。
一、如果函数无参数,这种情况就不用考虑参数的传递,多好。
二、通过寄存器组传递参数,调用函数把参数传入寄存器组里,在被调用函数里还要把参数从寄存器组里存入固定存储地址。
三、通过固定存储区传递参数,调用函数直接把参数传入固定的存储地址里,被调用函数从这里取参数就是了。
四、通过模拟栈传递参数,有点类似于通过寄存器组传递参数,不过代码的大小及执行的时间可就……。
由此看到,并不是说通过寄存器传递参数是最好的方法,有些情况下通过固定存储区传递参数或许会有更好的表现。(如代码大小,执行时间在某些情况也会更快(这里不再举例说明了,大家可自己想想))。