堆栈存储器如何用于功能和局部变量?


8

我想将一些值保存到EEPROM,还希望通过避免一些变量声明来释放SRAM,但是EEPROM存储器是按字节分配的。

如果要存储一个int值,则必须重复使用一些表达式。我以为我会为它们做一些功能。但是我担心,如果创建一个函数,它仍然会占用SRAM内存,最好声明一个int变量而不是使用EEPROM。

函数和局部变量如何存储在SRAM中?它是否仅存储闪存中功能指针的地址,还是所有变量和命令都存储在堆栈中?


4
请记住,EEPROM只能写入有限的次数,而读取则不受限制。根据AVR数据表,EEPROM仅具有100000个周期,这听起来很多,但是当您尝试将其用作SRAM时,它将仅持续很短的时间。
jippie 2014年

我的天啊!在那之后,EEPROM会失效吗?我要检查数据表!
纳菲斯2014年

闪存还具有生命周期。最好不要大量刻录程序。
纳菲斯2014年

在正常使用情况下,闪存和EEPROM的编号完全没有问题。当您开始使用方程式时,就像使用SRAM一样。
jippie 2014年

Answers:


4

仅函数的数据存储在堆栈中;它的代码停留在闪存中。您不能真正通过使用EEPROM来减少SRAM的使用,因为如您所见,EEPROM无法以相同的方式寻址。读取和存储EEPROM的代码也需要使用一些SRAM-可能与您要保存的SRAM一样多!EEPROM的写入速度也很慢,并且具有有限的生存期(每个字节的写入次数),这两者都使得存储通常放置在堆栈中的临时数据种类不切实际。它更适合保存不经常更改的数据,例如用于批量生产设备的独特设备配置,或捕获不频繁的错误以供以后分析。

编辑: 该函数没有堆栈,直到该函数被调用为止,所以是的,那是当函数的任何数据放入那里时。函数返回后发生的事情是不再保留其堆栈帧(其SRAM的保留区域)。最终它将被另一个函数调用重新使用。这是内存中C堆栈的图。当堆栈框架不再有用时,只需将其释放,即可重新使用其内存。


我在想这样,当调用函数时,只有其中的数据才存储在堆栈中。执行该功能后,将从堆栈/ SRAM中擦除数据。我对吗?
纳菲斯2014年

5

局部变量和函数参数存储在堆栈中。但是,这不是不使用它们的原因。计算机被设计为以这种方式工作。

堆栈存储器仅在功能激活时才使用。函数返回后,将立即释放内存。堆栈存储器是一件好事。

您不想将递归函数与许多递归级别一起使用,或在堆栈上分配许多大型结构。正常使用是可以的。

6502堆栈只有256个字节,但是Apple II可以正常工作。


因此,您的意思是该函数及其所有局部变量,参数和表达式将仅在调用时临时保存到堆栈中?否则它将保留在程序/闪存中?执行后,是否会将其从堆栈中擦除?我实际上是在谈论Arduino,因为它是Arduino论坛,但我没有提到。
纳菲斯2014年

不,只有函数的参数和局部变量在堆栈中。该功能的代码未保存在堆栈中。不要想太多。
Duncan C

5

AVR(传统上在Arduino板上使用的微控制器系列)是哈佛架构,这意味着可执行代码和变量位于两个单独的存储器中-在这种情况下为闪存和SRAM。可执行代码永远不会离开闪存。

调用函数时,返回地址通常被压入堆栈-例外情况是函数调用发生在调用函数的末尾。在这种情况下,将使用调用调用函数的函数的返回地址代替-它已经在堆栈中。
是否将其他任何数据放入堆栈取决于调用函数和被调用函数中的寄存器压力。寄存器是CPU的工作区域,AVR具有32个1字节寄存器。可以通过CPU指令直接访问寄存器,而SRAM中的数据首先必须存储在寄存器中。只有当参数或局部变量太大或太多而无法容纳在寄存器中时,它们才会被放入堆栈中。但是,结构始终存储在堆栈中。

您可以在此处 阅读有关AVR平台上GCC编译器如何使用堆栈的详细信息:https : //gcc.gnu.org/wiki/avr-gcc#Frame_Layout
阅读“帧布局”和“调用约定”部分。


1

在函数调用进入函数后,立即执行的第一个代码将堆栈指针递减等于函数内部临时变量所需空间的数量。关于这一点的妙处在于,所有函数因此成为可重入和递归的,因为它们的变量建立在调用程序的堆栈上。这意味着,如果中断中止了一个程序的执行并将其转移到另一个程序,则该中断也可以调用相同的函数,而不会相互干扰。


1

我一直在努力尝试编写一些示例代码,以演示此处的出色答案所讲的内容,但到目前为止没有成功。原因是编译器积极地优化了东西。到目前为止,即使在函数中使用局部变量,我的测试也根本没有使用堆栈。原因如下:


  • 编译器可以内联函数调用,因此返回地址可能根本不会被压入堆栈。例:

    void foo (byte a) { digitalWrite (13, a); } void loop () { foo (5); }

    编译器将其转换为:

    void loop () { digitalWrite (13, 5); }

    没有函数调用,没有使用堆栈。


  • 编译器可以在寄存器中传递参数,从而省去了将参数压入堆栈的麻烦。例:

    digitalWrite (13, 1);

    编译成:

    158: 8d e0 ldi r24, 0x0D ; 13 15a: 61 e0 ldi r22, 0x01 ; 1 15c: 0e 94 05 01 call 0x20a ; 0x20a <digitalWrite>

    参数被放入寄存器中,因此不使用堆栈(除了用于调用digitalWrite的返回地址以外)。


  • 可以将局部变量放入寄存器中,再次节省了使用RAM的麻烦。这不仅节省了RAM,而且速度更快。

  • 编译器会优化掉您不使用的变量。例:

    void foo (byte a) { unsigned long bar [100]; bar [1] = a; digitalWrite (9, bar [1]); } void loop () { foo (3); } // end of loop

    现在,这是分配400个字节的“酒吧”难道不是吗?不:

    00000100 <_Z3fooh>: 100: 68 2f mov r22, r24 102: 89 e0 ldi r24, 0x09 ; 9 104: 0e 94 cd 00 call 0x19a ; 0x19a <digitalWrite> 108: 08 95 ret 0000010a <loop>: 10a: 83 e0 ldi r24, 0x03 ; 3 10c: 0e 94 80 00 call 0x100 ; 0x100 <_Z3fooh> 110: 08 95 ret

    编译器优化了整个数组!它可以表明我们实际上只是在做a digitalWrite (9, 3),这就是它所产生的。


故事的寓意:不要试图超越编译器。


大多数非平凡的函数都使用堆栈来保存一些寄存器,以便可以使用它们来保存局部变量。然后,我们遇到了一个有趣的情况,其中函数的堆栈框架包含属于其调用方的局部变量。
Edgar Bonet 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.