单片机编程中关于堆栈的一些问题
扫描二维码
随时随地手机看文章
编译器在生成代码使用两个堆栈:一个是用于子程序调用和中断操作的硬件堆栈,一个是用于以堆栈结构传递的参数临时变量和局部变量的软件堆栈。硬件堆栈是从数据内存的顶部开始分配的,在硬件堆栈下面再分配一定数量的字节作为软件堆栈。硬件堆栈和软件堆栈均为向下生长型的堆栈(注意:这与51单片机相反)。
通常如果你的程序没有子程序调用也不调用象带有%f 格式的printf()等库函数,那么默认的16 字节应该在大多数的例子中能良好工作。在绝大多数程序中除了很繁重的递归调用程序再入式函数,最多40 个字节的硬件堆栈应该是足够的。
如果函数的调用层次太深,有可能会发生硬件堆栈溢出到软件堆栈中,改变了软件堆栈中数据的内容,同样,当定义了太多的局部变量或一个局部集合变量太多也有可能出现软件堆栈溢出到动态分配的数据区,两个堆栈都有可能溢出,如果堆栈溢出,会引起不可预测的错误。可以使用堆栈检查函数检测两个堆栈是否溢出。
在Target的页面中有一个Return Stack Sizi选项,用于指定硬件堆栈(保存函数返回值)的大小,通常如果子程序调用嵌套不深(不超过4层),那么使用默认的16字节就足够了,如果使用了浮点函数,则至少应设定为30个字节。在一般情况下,除了层次很深的递归调用及使用了%f格式说明符外,设定为40个字节就足够了。
硬件堆栈是从数据内存的顶部开始分配的,而软件堆栈是在它下面一定数量字节处分配。硬件堆栈和数据内存的大小是受在编译器选项中的目标装置项设定限制的。数据区从0x60 开始分配。在IO 空间后面是正确的。允许数据区和软件堆栈彼此相向生长。
如果你选择的目标装置带有32K 或64K 的外部SRAM,那么堆栈是放在内部SRAM的顶部而且向低内存地址方向生长。参考程序和数据内存的使用。任意一个程序失败的重要原因是堆栈溢出到其它数据内存的范围,两个堆栈中的任意一个都可能溢出,并且当一个堆栈溢出时会偶然产生坏的事情,你可以使用堆栈检查函数检测溢出情况 。
关于堆栈检查函数:
启动代码在硬件堆栈和软件堆栈的最低字节分别写进一个代码(0xaa),把这个代码称为警戒线。如果硬件堆栈和软件堆栈如果溢出过,则警戒字节的代码(0xaa)就会被改变,堆栈检查函数就是通过检查这两个堆栈的最低字节的代码是否被改变来判断两个堆栈是否溢出。通过调用_StackCheck(void)函数来检查堆栈溢出,如果警戒线字节中的代码仍然保持正确的值,那么函数检查通过,没有溢出。如果堆栈溢出,那么警戒线字节将可能被破坏,_StackCheck(void)函数检查到警戒线判断字节中的代码被改变,就判断相应的堆栈溢出(当程序堆栈溢出,程序可能运行不正常或偶然崩溃),该函数再调用函数_StackOverflowed(char c),如果参数是1,那么硬件堆栈有过溢出;如果参数是0,那么软件堆栈曾经溢出。
在使用堆栈检查函数时应注意以下几点:
1、在使用堆栈检查函数时,前必须用#i nclude "macros.h"预处理。
2、如果使用自己的启动文件,在ICCAVR6.20以后的版中,如果使用的启动文件中没有警戒线的内容,ICCAVR也会自动添加警戒线。而在ICCAVR6.20以前的版本中,必须自己添加该部分内容,否则生成的代码中堆栈分配将不带警戒线。
3、如果使用动态内存分配,必须跳过警戒线字节_bss_end来分配您的堆(即增加一个字节),详见内存分配函数说明
4、当_StackCheck(void)函数检测到警戒线字节被改变,则会调用一个默认的_StackOverflowed 函数来跳转到程序存储器0的位置(复位向量地址)。可以指定或重新编写一个新的函数来代替它,例如可以用新函数来指示是哪个堆栈溢出等,但这个函数也不可能执行太多的功能或让程序恢复到正常状态。因为堆栈溢出后,会更改掉一些有用的数据,引起不可预测的错误,甚至使程序死机。
下面用一个简单的实例来说明堆栈检查函数的作用:
main( )
{
init( ) //调用初始化程序
float a,b;
a=1.0;
b=1.0;
printf("a = %fn", a);
printf("b = %fn", b);
_StackCheck( ); //调用堆栈检查函数
}
_StackOverflowed(char c)
{
if (c == 1)
puts("trashed HW stack"); //硬件堆栈溢出
else
puts("trashed SW stack"); //软件堆栈溢出
}
扩展阅读:AVR单片机一些学习笔记