Cx51程序设计的堆栈空间计算方法:子函数调用和中断函数调用的堆栈计算方法

Cx51程序设计的堆栈空间计算方法
技术分类: 嵌入式系统 | 2010-12-14
21ic
  引言
  用 C语言进行 MCS51 系列单片机程序设计是单片机开发和应用的必然
趋势。 Keil 公司的 C51编译器支持经典 8051和8051派生产品的版本,通
称为 Cx51。应该说, Cx51是C语言在 MCS51 单片机上的扩展,既有 C语言
的共性,又有它自己的特点。本文介绍的是 Cx51程序设计时堆栈的计算
方法。
1堆栈的溢出问题
MCS51 系列单片机将堆栈设置在片内 RAM中,由于片内 RAM资源有
限,堆栈区的范围也是有限的。堆栈区留得太大,会减少其他数据的存
放空间,留得太少则很容易溢出。所谓堆栈溢出,是指在堆栈区已经满
了的时候还要进行新的压栈操作,这时只好将压栈的内容存放到非堆栈
区的特殊功能寄存器( SFR)中或者堆栈外的数据区中。特殊功能寄存
器的内容影响系统的状态,数据区的内容又很容易被程序修改,这样一
来,之后进行出栈操作(如子程序返回)时内容已变样,程序也就乱套
了。因此,堆栈区必须留够,宁可大一些。要在 Cx51程序设计中防止堆
栈的溢出,要解决两个问题:第一,精确计算系统分配给用户的堆栈大
小,假设是 M;第二,精确计算用户需要堆栈的大小,假设是 N。要求
M≥N,下面分别分析这两个问题。
2计算系统
  分配给用户的堆栈大小 Cx51程序设计中,因为动态局部变量是长驻
内存中的,实际上相当于局部静态变量,即使在函数调用结束时也不释
放空间(这一点不同于标准 C语言)。 Cx51编译器按照用户的设置,将
所有的变量存放在片内和片外的 RAM中。片内变量分配好空间后,将剩
下的空间全部作为 堆栈空间 ,这个空间是最大可能的堆栈空间。当
然,因为 Cx51是一种可以访问寄存器的 C语言(特殊功能寄存器),因
此可在程序中访问 SP,将堆栈空间设置得小一点。不过,一般没有人这
么做。本文只是讨论放在片内 RAM的变量。我们把变量分为两种情况:
  ① 用作函数的参数和函数返回值的局部变量。这种变量尽量在寄
存器组中存放。为了讨论方便,假设统一用寄存器组 0,具体的地址为
0x00~0x07。最多可以传递 3个参数,如果参数的个数比较多,就将多余
的参数放到内存(0x08以后的地址)中存放。这里,假设每个函数的参
数都不大于 3个。
  ② 我们在程序中定义的全局变量,以及不是用作函数的参数和函
数返回值的局部变量。以上两种变量在内存中 0x08地址以后存放,存放
完毕后将堆栈指针 SP指向分配了变量的片内 RAM的最后一个字节。因为
MCS51 单片机的堆栈是一种满递增堆栈且堆栈的宽度为 8位,所以在需要
压栈操作时将堆栈指针先加 1,后入栈有效内容。有了以上规则,就可
以精确地计算出系统分配给用户的堆栈空间。以求两个数的最大公约数
和最小公倍数的函数为例,代码如下:
  这段程序中资源的分配情况如下:一个全变量 M(无符号字符型)
存放最大公约数;主函数中定义一个局部变量 n(无符号字符型)存放
最小公倍数;求最大公约数的函数 unsigned char max(unsigned char
a, unsigned char b) ,有两个参数 a和b;求最小公倍数的函数
unsigned char min(unsigned char a, unsigned char b) ,有两个参
数a和b,并且定义了一个变量 k存放函数的返回值。可以由此计算出系
统分配给变量的空间。函数的参数和返回值在工作寄存器组中存放,所
以不占用 0x08地址以后的空间。系统只给变量 M和变量 n分配存储空间,
这两个变量占两个字节(地址为 0x08和0x09),则堆栈指针 SP应该指向
0x09。 Cx51系统编译后生成代码的系统资源占用情况如下:全局变量M
的地址为 0x08, n的地址为 0x09, SP的值为 0x09。这与我们的计算结果
相符。
3计算用户需要堆栈的大小
  堆栈区到底留多大才算足够呢? Cx51程序设计中,用户需要堆栈
的大小可以从普通子函数和中断子程序的嵌套层数来计算。普通子函数
的调用比较简单,每次调用时就是将函数的返回地址保存在堆栈中,这
个地址占两个字节。函数嵌套调用时,从最内层的子函数算起,总的堆
栈需求字节数为嵌套的层数乘以 2
。中断子程序的堆栈需求分为两种情
况:
  ① 中断子程序使用中断发生前的寄存器组。在中断发生时,保存
中断子程序的返回地址需要 2个字节。中断发生后,在中断子程序中系
统会自动进行如下操作:将 ACC、 B、 DPH、 DPL、 PSW、 R0~R7共13个寄存
器压栈。加上中断返回地址,中断的堆栈需求为 15个字节。

中断子程序使用自己专用的寄存器组。这种情况下不需要保存 R0~R7
的内容,可以减少堆栈需求,其他的内容仍需要压栈保护。中断发生
时,保存中断子程序的返回地址需要 2个字节。中断发生后,在中断子
程序中系统会自动进行如下操作:将 ACC、 B、 DPH、 DPL、 PSW共5个寄存
器压栈。加上、中断返回地址,这种堆栈的需
求为7个字节。
但是这种情况应该注意:如果中断子程序中调用子函
数,且函数需要参数和返回值,则被调用的子函数和中断子程序要使用
相同的寄存器组,否则会出现不可预料的后果。以一个温度测试系统为
例。系统采用 8051作为处理器,温度信号在 A/D转换结束后通过外部中
断0提醒单片机接收处理。定时中断 0作为监控程序,中断周期为 20
ms。温度信号可以自动测量(每秒一次)或者手动测量(按测量键后测
量),这两种测量方法可以通过控制键切换。中断子程序和普通子函数
的嵌套情况为:在定时中断程序中调用显示子程序,外部中断 0内部没
有函数调用。部分程序如下:
  接下来分析这段程序的最大堆栈需求。假设定时器 0中断时,调用
了显示函数 void leddisp(unsigned char *pt) ,在调用显示函数时 A/D
转换结束发生了外部中断 0的中断。这时应该是程序对堆栈的最大需
求,堆栈的大小是:定时器 0(15字节)+显示函数( 2字节)+外部中
断0(7字节)= 24字节。
  结语
  通过精确的计算编译系统分配给用户的 堆栈空间 和用户自己最大的
堆栈需求,不仅能从根本上解决堆栈溢出的问题,还可以很好地安排单
片机比较紧张的资源。此外,通过在片内存储器存放适量局部变量,还
可以有效地提高软件的执行速度。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值