可重入與不可重入,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次,但足以說明整個過程。
總結:
遞歸與函數的連環調用沒有任何差別!都是遵循參數入棧,返回地址入棧的操作。
只不過遞歸調用的是自己。