前幾天在寫C51程序時用到了遞歸,簡單程序如下:
voidWRITE_ADD(ucharaddr,ucharwbyte){START();//先發(fā)送起始信號WRITE_BYTE(0xa0);//設備地址+W命令if(!ERROR_Flag)//正確收到應答{WRITE_BYTE(addr);//寫入地址}else{ERROR_Flag=0;//清錯誤標志WRITE_ADD(addr,wbyte);//重新寫入}if(!ERROR_Flag)//地址收到正確應答{WRITE_BYTE(wbyte);//發(fā)送要寫入的數據}else{ERROR_Flag=0;//清錯誤標志WRITE_ADD(addr,wbyte);//重新寫入}if(!ERROR_Flag)//正確收到應答{STOP();//停止}else{ERROR_Flag=0;//清錯誤標志WRITE_ADD(addr,wbyte);//重新寫入}}
編譯時出現如下警告:
warning C265: '_WRITE_ADD': recursive call to non-reentrant function(循環(huán)調用了非可重入函數)。
經過查找資料之后,解決方法是在函數后加入關鍵字,使函數變成可重入函數:
返回值 函數名(形參) reentrant
上面的函數是有錯誤的,可重入函數不能傳遞bit類型的變量。在多任務系統(tǒng)中,可重入函數也不要用全局變量,多個函數同時調用時可能會使變量出現多個值,但是在單任務系統(tǒng)中,個人認為某些時候下是可以利用的。只要不出現改變變量值的情況。
一、可重入函數
首先對重入函數進行一下說明。
可重入函數主要應用在多任務環(huán)境中,一個可重入的函數簡單來說就是可以被中斷的函數。也就是說這個函數執(zhí)行的任何時刻中斷它,轉入另一段代碼,返回控制時不會出現什么錯誤,而不可重入的函數由于使用了系統(tǒng)資源,比如全局向量、中斷向量表等,如果函數被中斷的話可能會發(fā)生錯誤。
在Keil手冊中對可重入函數的解釋為:一個可重入函數可以在同一時間被幾個進程共享。當一個函數可重入運行時,別的進程可中斷執(zhí)行,并開始執(zhí)行相同的可重入函數。正常情況,C51編譯器重的函數不能重入。原因是函數的參數和局部變量保存在固定的存儲區(qū)中。通過reentrant函數屬性允許聲明函數可重入,因此可重復調用??芍厝牒瘮悼梢员贿f歸調用,可同時被兩個或多個進程調用??芍厝牒瘮到洺T趯崟r應用或者在中斷和非中斷必須共用一個函數的情況下被使用。如果函數定義為屬性reentrant,那么這個可重入函數,一個可重入的堆棧區(qū)同時在內部和外部存儲區(qū)模擬,這是由存儲模式來決定的。如果是SMALL模式,則在idata存儲區(qū)模擬可重入堆棧。如果是COMPACT模式,那么在pdata存儲區(qū)模擬可重入函數堆棧。如果是LARGE模式可重入函數在xdata存儲區(qū)模擬可重入堆棧。
二、可重入函數與函數的可重入
對此的詳細解釋引自:http://www.keil.com/support/docs/1873.htm
可重入函數與函數的可重入是兩個不同的概念。
在C51中如果我們定義以下函數:
int function(int a, int b, int c) compact reentrant
{
long x, y, z;
....
}
由于聲明為reentrant屬性,因此函數為可重入函數,其參數(a,b,c)和局部變量(x,y,z)存儲在模擬堆棧(simulated stack),由于是compact模式,因此在pdata區(qū)。
如果沒有特意的聲明compact,則會默認的為small,會在idata區(qū)模擬堆棧。如果是large則會在xdata區(qū)。這是可重入函數。(單片機的“硬件棧”,其實只有一個,就是我們通常說的SP,它是在內部RAM中的)
但有些函數未聲明為reentrant,但是可重入的。大多是以匯編來編寫的,其參數和局部變量存儲在寄存器中(data),在C51的庫函數中有很多這樣的函數,它們是可重入的,但未用reentrant聲明。
三、解釋
普通的函數的形參和局部變量的存儲是存在全局變量區(qū)(在《全局變量和局部變量存儲》中詳細的講解),在遞歸調用的時候上一層次的局部變量會被本層次調用沖掉。通過reentrant,編譯器會形成模擬棧為形參和局部變量分配內存。如果函數遞歸或者嵌套的次數太多,也會發(fā)生棧溢出(對于該模擬棧的大小可以在STARTUP.A51中修改)。
對于重入函數的模擬棧與單片機內的棧不同,模擬棧是由最頂端往下遞減的,而sp則是grow up的。
在函數的遞歸或者通過函數指針調用函數時,如果被調用的函數中有字符串常量,有時會提示“WARNING13:RECURSIVECALLTOSEGMENT”
其具體的解決方法見轉載文章“Keil"RECURSIVECALLTOSEGMENT"徹底解決”。