可重入与不可重入,reentrant关键字
Keil中帮助文档对此又详细的介绍
这一段的意思是,在Keil中,正常情况下函数调用是通过固定寄存器传递参数。因此当出现递归和类似的情况时,寄存器中参数可能会被覆盖。
如果想要通过堆栈来传递参数则需要使用reentrant参数。这个用来传递参数的堆栈叫方针堆栈,与硬件堆栈不同。
这一段的意思是,在Keil中,正常情况下函数调用是通过固定寄存器传递参数。因此当出现递归和类似的情况时,寄存器中参数可能会被覆盖。
如果想要通过堆栈来传递参数则需要使用reentrant参数。这个用来传递参数的堆栈叫方针堆栈,与硬件堆栈不同。
如上图,不同的存储模式下,仿真堆栈的名字是不一样的。
因此,系统里面就会存在2个堆栈。一个是系统用来传递参数的堆栈,如图中的?C_XBP,用DPTR寄存器进行操作。(再次强调,若没有定义reentrant,参数的传递通过R1-R7寄存器进行)。
另外一个是大家比较熟悉的系统用来维护函数调用与中断调用等情况下压栈和出栈操使用的堆栈。如图中的?STACK。用SP寄存器进行操作。
下面结合具体的实例,分析递归调用时两个堆栈的情况。
如果不定义reentrant这个关键字,则编译时会出现如下错误,很显然参数在传递时会因为被覆盖导致表达式计算不是真是想要的。
当n=1时,第一次调用。此时keil会将参数n存放入 R6和R7中。
调用mul函数,此时函数的返回地址会被放入?Stack中,该堆栈从0x0A开始(由系统分配,因此在编程时一定要注意该堆栈的大小)
进入mul函数,此时会将存在R6与R7中的参数保存入仿真堆栈中?C_XBP中,该栈从0x80往下生长
该堆栈的工作方式为先往下腾出位置,在把值写进去。而框框中的操作数0xFFFE则是补码的形式,跟踪C?ADDXBP可以看出,该语句就是将堆栈往下移动一个空间
当需要对该参数进行操作时,会从堆栈中取出参数进行操作
至此,可以看出增加了reentrant关键字之后,其对局部变量的操作都是在堆栈中完成,这也是能够完成函数重入的关键。
如果参数不为0, 执行return mul(n-1);此时又会将n-1的参数利用R6与R7存储,然后调用函数,在?_Stack中存入返回地址,调用完之后进行计算,再返回
这个时候n=n-1后,n=0;return 1,此时会利用R6与R7进行结果输出。
在进行结果返回时,需要清空相应的仿真堆栈空间,即局部参数用之后,系统回收堆栈空间
执行RET后,直接跳到函数返回的地方
函数返回后,将数据传给sum(X:0x0000),需要利用DPTR进行传递数据。
该过程因为参数设定为1,只调用了2次,但足以说明整个过程。
总结:
递归与函数的连环调用没有任何差别!都是遵循参数入栈,返回地址入栈的操作。
只不过递归调用的是自己。