历史上的今天
返回首页

历史上的今天

今天是:2024年11月23日(星期六)

2018年11月23日 | 软件堆栈和硬件堆栈

2018-11-23 来源:eefocus

栈是一种具有先入后出特性的数据结构,前面说过,这种特性常常用来帮住我们“原理返回”或者“保持原样”。试想,当我们第一次来到一个陌生的城市,走在陌生的街道上,寻找一个陌生的目标,最令我们有安全感的莫过于仔细记录走过的每一个街道、穿过的每一个路口--这种安全感来源于潜意识里“万一找不到目的地就原路返回”的想法。记得20世纪90年代,有一首家喻户晓的流行歌曲《星星点灯》中曾这样唱到“星星点灯...为迷失的孩子,照亮来时的路”。


“找到来时的路”这种想法是人们基本的求生本能,对有人类编写的C语言编译器来说,也是这样--面对一层一层复杂嵌套关系的函数调用,编译器总是试图记录下我们调用的过程,以便“找回回去的路”。栈就在这种场合中,得到了广泛的应用。


C语言支持函数的调用,这完全得益于栈式分配策略的使用。所谓栈式分配,抛去复杂的技术细节,简单说来,就是将函数内部使用的种种信息(例如,局部变量)在发生函数嵌套调用时,压入栈中“记录下所走过的路”。这样,当调用的函数运行结束需要返回时,编译器就能很容易从栈中找到“来时的路”。使用模拟的方法,我们来具体看看这一过程。


我们假设:一个函数中所有牵涉到的局部信息都被包含在一个与函数同名的接节点中。当我们在某一个函数中发生了对另外一个函数的调用,就将本函数的局部信息压入栈中--也就是将以该函数命名的结点压入栈中:当我们从某一函数中返回,就从栈中弹出一个结点。观察一段代码的函数调用情况,了解编译器如何借助来实现函数的嵌套调用。


//这是一段演示用的伪代码,包含了一些函数并设置了一些断点便于观察


//函数的细节已经省略,只保留了对其他函数的调用关系


//函数A


void FuncA(void)


{


//没有任何针对其他函数的嵌套调用


/*断点A1*/


}


//函数B


void FuncB(void)


{


...


FuncA();//调用了函数A


/*断点B1*/


...


}


//函数C


void FuncC(void)


{


...



FuncB();//调用了函数B


/*断点C1*/


FuncA();//调用了函数A


/*断点C2*/


...


}


//主函数


void main(void)


{


...


/*在这里,我们设置一个程序断点,称为断点1*/


FuncA();


/*断点2*/


FuncB();


/*断点3*/


FuncC()


/*断点4*/


...


}


当程序运行到断点1时,因为还没有发生任何函数调用(我们假设测试环境中没有使用到操作系统,因此不存在操作系统调用main函数的问题,也就是不存在将操作系统相关的内容压入栈中的问题),此时,栈是空的,如图13-10(a)所示。


程序继续运行,发生函数调用--FuncA(),并在其中遇到了断点A1。因为此时发生了函数的调用,结点main被压入栈中。此时只是一个结点,如图13-10(b)所示。程序运行到断点2,系统从函数FuncA()返回,因此,将结点main弹出,如图13-10(c)所示。


继续运行程序,直到再次遇到断点A1。此时,栈中有两个元素,从栈顶向下分别是FuncB、main。这说明,在此之前,发生了两次调用:首先是main调用了FuncB,紧接着在FuncB中调用了FuncA,如图13-11(a)所示。当我们遇到断点B1时,程序已经从FuncA中返回,因此弹出了栈顶元素FuncA,如图13-11(b)所示。经过断点3时,结点main也被弹出,栈再次成为空栈,如图13-11(c)所示。


当程序第三次执行到断点A1时,由于发生了三次函数调用,因此,栈中有三个结点FuncB,FuncA和main,如图13-11(d)所示。再次经过断点B1时,程序从函数FuncA中返回,因此弹出了栈顶元素FuncB,如图13-11(e)所示。程序继续执行,从函数FuncB中返回遇到断点C1时,结点FuncC被弹出,如图13-11(f)所示。


从断点C1向后执行,调用函数FuncA第四次遇到断点A1,结点FuncA再次被压入栈中,如图13-12(a)所示。程序从函数FuncA返回,经过断点C2 时,弹出栈顶指针FuncA,如图13-12(b)所示。当我们遇到断点4时,程序已经回归到主函数main(),栈中最后一个结点被弹出,称为空栈,如图13-12(c)所示。


通过上面的模拟,我们展示了C编译器利用栈实现函数嵌套条用的原理。详细情形大家可以参考编译原理的相关内容,这里就不再深入。前面,我们知道,没当发生一次函数调用,编译器都要保存当前的相关信息。这一信息至少包括两大部分:其一,用于描述程序从函数调用中返回时,返回到哪个函数位置;另一部分,用于描述与当前函数有关的一些局部信息(比方说局部变量)。在ICC中,编译器将两个独立开来,分别使用两个栈来保存。保存函数返回地址(也就是第一部分)的栈被称为硬件栈(Hardware Stack 或者 Resturn Stack)。保存当前函数局部相关信息的栈,我们称之为软件堆栈(Software Stack)。


将原来完整的结点信息一分为二,究竟是出于怎样的考虑呢?我们知道,单片机的内存容量有限(ATMeag48的SRAM仅有512个字节),不可能允许无限制的函数嵌套,必须对栈的尺寸加以限定。在正常情况下,一个结点中包含的信息总是包含定长和不定长两个部分。其中,表示函数返回地址的部分由于指针长度固定为2个字节,因此属于定长部分。将其单独作为一个结点保存在硬件堆栈中,便于规定函数允许嵌套的最大深度(例如,当我们设定HW Stack的最大尺寸是16时,就表示所允许的函数嵌套深度最大为8层)。出去函数返回地址,我们将其余的变长信息统一放在软件堆栈中,而这一部分的尺寸我们是无法预计的--栈究竟需要多大空间,完全取决于用户在函数中定义了多少局部信息(比方说局部变量)及函数嵌套了多少层,因此,我们总希望能给HW Stack提供尽可能大的空间。


出于以上考虑,ICC系统采用硬件栈和软件栈分开存储的方式。编译器要求硬件栈的大小必须在程序编译器前由用户给定(在ICC集成开发环境中,有Compile Option--Target选项卡的Return Stack Size选项卡来设定),如图13-13所示。硬件栈位于SRAM的高地址,采用向上生长的方式,大小直接受到从栈底地址开始至存储器顶端所剩余的空间限制。这一空间大小恰好等于Return Stack所设置的数值。软件栈与硬件栈背靠背存放,采取向下生长的方式,虽然其大小受到诸多因素的共同影响,但最大尺寸在编译完成后也是确定的,如图13-14所示。


考虑到存储器的大小的限制,我们在编写单片机的程序时,一定要精打细算。一个程序,当系统中使用了大量的中断资源,并且允许了中断嵌套的存在,那么在极端的情况下,中断处理程序就很容易发生嵌套现象。此时适当扩大硬件堆栈的大小,支持较大的函数嵌套深度,往往能解决很多莫名其妙的跑飞问题。与拥有丰富存储器资源时的状况不同,由于局部变量在函数发生嵌套时,都要占用软件堆栈空间,因此大量使用使用局部变量或者使用了占用空间颇为可观的局部数组(也包括体积巨大的结构体),在嵌套深度较大时,都有可能造成向下生长的软件堆栈侵入其他存储区域(详细情形阅读ICC的帮助文档),导致某些变量意外修改、程序跑飞等现象。解决这一问题的方法其实很简单,在某些局部变量占用空间较大的情况下,将其通过关键static声明为静态变量--这样即保证了变量的局限性,又避免了将这些内容压入软件栈中(静态局部变量在存储时和全局变量没有本质区别,采用的都是 静态分配),只不过每次使用这些变量之前都要记得补充必要的初始化代码。


推荐阅读

史海拾趣

Accuride公司的发展小趣事

在2009年,Accuride公司面临了严重的财务困境,其美国公司申请了破产保护。然而,这一困境并未击垮Accuride,反而成为其重生的契机。通过与债权人达成协议,Accuride成功地将公司的大部分所有权转让给债券持有人,并进行了重组。这一举措不仅缓解了公司的财务压力,还为其未来的发展奠定了坚实的基础。

Hexawave公司的发展小趣事

在经营过程中,Accuride公司也进行了一系列子公司出售和业务调整。例如,它曾宣布出售其子公司Fabco汽车公司。这一举措有助于Accuride公司更加专注于其核心业务,优化资源配置,提升整体运营效率。同时,通过出售子公司,Accuride也获得了一定的资金回流,为公司的未来发展提供了资金支持。

Gamma Microelectronics ( APM )公司的发展小趣事

为了进一步满足市场需求,G24i在英国威尔士卡迪夫建成了世界上第一条25MW的大规模生产基于钛衬底的DSSC基地。这一基地的建成标志着G24i在DSSC领域实现了从研发到生产的全链条覆盖。大规模生产不仅降低了产品的制造成本,也提高了生产效率和质量稳定性。G24i通过不断优化生产流程和提升管理水平,确保了产品的市场竞争力。

埃派克森微电子(Apexone)公司的发展小趣事

作为一家具有社会责任感的企业,埃派克森微电子在发展过程中始终关注社会公益事业。在四川汶川地震发生后,公司积极发起救援捐赠活动,通过中国扶贫基金会为灾区捐款10万元,帮助受灾民众度过难关。这一行动体现了埃派克森的社会责任感和人文关怀精神,也为公司在社会中树立了良好的形象。

这五个故事展示了埃派克森微电子在电子行业中的发展历程和取得的成就。从创新起步到专利技术的突破与商用,再到业绩的连续增长和国际化步伐的加快,以及积极履行社会责任的公益行动,都充分展现了埃派克森微电子的实力和担当。未来,随着科技的不断进步和市场需求的不断变化,埃派克森微电子将继续保持创新精神,不断提升产品性能和市场竞争力,为电子行业的发展做出更大的贡献。

台湾诚阳(BC)公司的发展小趣事

在电子行业的激烈竞争中,台湾诚阳(BC)公司凭借其对技术的敏锐洞察和持续创新,成功研发出一款具有颠覆性的电子产品。这款产品不仅具有高性能和稳定性,还集成了多项前沿技术,满足了市场对于高效、便捷的需求。凭借这一创新产品,台湾诚阳迅速在市场中脱颖而出,赢得了众多客户的青睐。

Hamamatsu公司的发展小趣事

在追求经济效益的同时,台湾诚阳(BC)公司也积极履行企业社会责任。公司关注环保和可持续发展,致力于推广绿色电子产品和节能减排技术。同时,公司还积极参与社会公益事业,为当地社区的发展做出贡献。这种对社会责任的承担和关注,使得台湾诚阳在业界树立了良好的形象,赢得了社会各界的认可和尊重。

请注意,以上故事仅为虚构示例,并不代表台湾诚阳(BC)公司或任何真实公司的实际情况。如果您对该公司有进一步的了解需求,建议直接访问其官方网站或查阅相关新闻报道。

问答坊 | AI 解惑

一款简单容易制作的调频发射机

1)高频三极管V1和电容C3、C5、C6组成一个电容三点式的振荡器   2)C4、L组成一个谐振器:谐振频率就是调频话筒的发射频率,根据图中元件的参数发射频率可以在88~108MHZ之间,正好覆盖调频收音机的接收频率,通过调整L的数值(拉伸或者压缩线 ...…

查看全部问答>

wince6.0,RAS拨号的问题。

写一个用RAS拨号连接的程序,连接成功了,但是中间状态显示有问题, LRESULT CGPRSDlg::WindowProc(UINT mesaage,WPARAM wParam,LPARAM lParam) {    if (mesaage==WM_RASDIALEVENT)      {      ...…

查看全部问答>

ADS环境下延时时间计算?

对ADS不是很熟悉,以前用Keil的时候可以软件仿真直接查看设定频率下延时函数的延时时间,但不知道ADS下是怎么看的,比如主频是60M,那下面这段延时函数的延时时间是多少呢?太菜了,还问这么低级的问题。取t=1情况计算。谢谢了!ARM7内核 void Del ...…

查看全部问答>

2410硬件时钟一天慢几十分钟问题

请教各位大侠,我的2410硬件时钟在用市电开机状态每天慢30-60分钟,在关机状态每天慢1妙,请高手帮分析分析,谢谢!…

查看全部问答>

汽车音响功放选择中的小窍门

无论是否汽车音响的发烧友,面对这些充满金属质感的器材都会被其特有的磁性所深深吸引。但或许对于大多数人来说,如何选购称心满意的音响器材却是个难题。 下面我们将为大家介绍一些功放选择中的几点小窍门。 确定同一基准比较功放功率 功率是音 ...…

查看全部问答>

运放

如附件,此运放可实现电流Ip输出:电流Ip和电压Vdem是有一定关系的,为什么我搭的电路不好使,有人可以帮忙分析一下吗,这电路的原理是什么 …

查看全部问答>

征集DIY数控实验电源的显示面板设计

为更丰富本次DIY数控电源的活动,又恰逢站方推出430铁电实验板的团购,为让更多的网友能够参与到本次活动中,现征集DIY数控实验电源的显示面板设计,本面板独立于电源本身,只是一个显示操控装置,与电源板通过串口互联,用协议和指令控制电源板的 ...…

查看全部问答>

msp430datasheet

msp430 x11x datasheet…

查看全部问答>

msp430 lauchpad i/o口 有上拉下拉电阻么

如题,求解。 还是说板子上 有外接的上拉下拉电阻 。 user\'s guide上 那个PxREN 不就是配置上拉 下拉电阻的么, 但我看有的视频教程里为什么说 ,他的io口 是普通的双向io, 没有上拉下拉电阻。 …

查看全部问答>

9013 8050 8550三极管作开关用时频率可达到多大

013 8050 8550三极管作开关用时频率可达到多大0分 如题,我要用单片机io产生的pwm控制一个大功率led的亮度,用三极管做一个简单的驱动电路,大家有什么好建议…

查看全部问答>