堆棧溢出的預防方法

 

MCS—51系列單片機堆棧設置在片內RAM中,由於片內RAM資源有限,故堆棧區的範圍也是有限的,堆棧區留得太大,將減少其他的數據存放空間,留得太小很容易溢出。所謂堆棧溢出,是指“堆棧區已滿時還要進行新的壓棧操作”,這時只好將壓棧的內容存放到非堆棧區的特殊功能寄存器中或存入堆棧外的數據的數據區中。特殊功能寄存器的內容影響到系統的狀態,數據區的內容很容易被子程序修改,這樣一來,當以後進行出棧操作時,內容已變樣,程序也就亂套了。因此,堆棧區必須留夠,只能大一些,一點兒也不能小。堆棧區到底留多大才算足夠呢?這是可以計算出來的。調用子程序和中斷響應後進入中斷子程序,均要將返回地址壓入堆棧,這要用去堆棧中的兩個字節空間,每個PUSH指令要用去一個字節空間。因此,就可以計算出每一個子程序對堆棧空間的需求量。由於子程序可以嵌套,故計算時要從最底層的子程序開始。

一個不調用其他子程序的低級子程序對堆棧的需求爲PUSH指令的最大深度再加兩個字節返回地址;一個調用若干其他子程序的高級子程序對堆棧的需求除去PUSH指令需求和返回地址外,還要加上各個被調用子程序中的最大需求量。例如有三個子程序A1,A2,和A3,它們的結構如下:

A1:               PUSH              ACC                A3:               PUSH              ACC

                        PUSH              PSW                                       PUSH              PSW

                        .                                                                      .                      

                        .                                                                      .                      

                        .                                                                      .                      

                        LCALL           A2                                           LCALL           A1

                        .                                                                      .                      

                        .                                                                      .                      

                        .                                                                      .                      

                        POP                PSW                                       LCALL           A2

                        POP                ACC                                       .                      

                        RET                 .                                                                     

A2:               PUSH              B                                             .                      

                        .                                                                      POP                PSW

                        .                                                                      POP                ACC

                        .                                                                      RET                

                        POP                B                                                                    

                        RET                                                                                       

在這三個子程序中,A2是一個最柢級的子程序,它有一條PUSH指令,故A2子程序需要3個字節堆棧空間才能正常運行。A1子程序有兩條PUSH指令,加上返回地址,已經用去4字節堆棧空間,還要調用A2子程序,爲此還需要3個字節,故A1子程序必須動用7字節的堆棧空間。A3子程序既有PUSH指令,又調用兩個子程序,則A3子程序對堆棧的需求量由本身PUSH指令數加上2字節返回地址,再加若干低級子程序中最大需求量來決定。在這裏A3本身需要4字節(2個PUSH指令加返回地址),A1要7字節,A2要3字節,取最大值7字節,故A3子程序共需要11字節的堆棧空間,方能正常運行。如果A1子程序在調用A2子程序之前執行了兩條出棧指令(POP),在調用A2子程序之後再沒有壓棧操作,則A1子程序對堆棧的總需求將減少到5字節。

用上述方法將所有子程序和中斷子程序的堆棧需求量均計算出來後,就可以計算出系統對換堆棧的極限需求了。

系統復位後設定堆棧指針。這時堆棧是空的,系統程序的主程序(又稱背景程序、後臺程序)由初始化程序段和無限循環(監控循環、踏步循環和節電待機街環等)構成,它們之中可能要調用若干子程序,找出這中間對堆棧需求最大的子程序,將所有的低級中斷子程序進行比較,找出其中對堆棧需求最大的低級中斷子程序,將它對堆棧的需求量作爲低級中斷對堆棧的最大需求量。用同樣的方法,找出高級中斷對堆棧的最大需求量。

系統對堆棧的極限需求量即爲主程序最大需求量加上低級中斷的最大需求量,再加上高級中斷的最大需求量。這裏是基於一種最不利的假設:當主程序運行到堆棧需求最大的時刻響應了低級中斷;當低級中斷運行到堆棧需求最大的時刻,又發生了高級中斷。

按以上方法計算出系統的極限需求後,便可以設定合適的堆棧位置了。如果RAM資源緊張,可以比以上計算空間減少一些,一般也能對付,因爲最不利的情況發生的概率極小,但終歸埋下了一個隱患。

按以上方法計算出來的堆棧需求量很大,系統實在無力劃出那樣大的堆棧區,但又不願冒險減少堆棧區,這時惟一的出路就是減少系統對堆棧的需求量,常用以下方法實現,即:

(1)    取消部分子程序。如果一個子程序只有一個“用戶”(即只被一處程序調用),就將該子程序取消,將過程體直接插入調用處。這個子程序取消後,本身對堆棧的需求已免去,同時可以減少調用程序本身的嵌套深度。對於很短的子程序,可以取消,用定義“宏“的方式將過程體直接插入有關的地方,從而減少對堆棧的需求。

(2)    儘量不用堆棧來傳遞參數和結果,即減少PUSH指令的使用,改用指定的單元傳送同樣可以達到目的。最好用累加器、B寄存器等來傳送,增加利用係數,減少堆棧需求。

(3)    子程序一律不負責保護主程序的現場,可以做到主動將可能被破壞的信息保護好,完全不必由子程序來壓棧保護。在很多情況下,累加器的內容和狀態寄存器的內容並不需要保護,有時它們的內容就是供子程序使用的參數,完全可以被使用掉,並在子程序結束時帶回出口信息。

中斷子程序是在主程序完全沒有準備的情況下運行的,故對主程序的現場必需加以保護。這和一般子程序不同。當中斷子程序本身對主程序現場完全沒有影響時,也不必保護現場。另外,影響範圍有多大就保護多大,不必什麼都壓棧保護,增加堆棧的開銷。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章