堆栈溢出的预防方法

来源:互联网 发布:广发华福软件 编辑:程序博客网 时间:2024/04/30 14:41
 

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)    子程序一律不负责保护主程序的现场,可以做到主动将可能被破坏的信息保护好,完全不必由子程序来压栈保护。在很多情况下,累加器的内容和状态寄存器的内容并不需要保护,有时它们的内容就是供子程序使用的参数,完全可以被使用掉,并在子程序结束时带回出口信息。

中断子程序是在主程序完全没有准备的情况下运行的,故对主程序的现场必需加以保护。这和一般子程序不同。当中断子程序本身对主程序现场完全没有影响时,也不必保护现场。另外,影响范围有多大就保护多大,不必什么都压栈保护,增加堆栈的开销。

原创粉丝点击