¿Cómo se usa la memoria de pila para funciones y variables locales?

8

Quería guardar algunos valores en la EEPROM y también quería liberar SRAM evitando algunas declaraciones de variables, pero la memoria EEPROM es byte-by-side.

Si quiero almacenar un valor int, tengo que usar algunas expresiones repetidamente. Pensé que haría algunas funciones para esos. Pero me preocupa que, si creo una función, todavía ocuparía la memoria SRAM, mejor declaro una variable int en lugar de usar EEPROM.

¿Cómo se almacenan las funciones y las variables locales en SRAM? ¿Solo almacena la dirección del puntero de función de la memoria flash o todas las variables y comandos se almacenan en la pila?

Nafis
fuente
44
Recuerde que EEPROM solo se puede escribir por un número limitado de veces, leerlo es ilimitado. De acuerdo con la hoja de datos de AVR, EEPROM solo tiene 100000 ciclos, lo que suena mucho, pero cuando intenta usarlo como SRAM, solo durará un período bastante corto.
jippie
¡DIOS MIO! Después de eso, ¿será inútil la EEPROM? ¡Voy a consultar la hoja de datos!
Nafis
La memoria Flash también tiene un ciclo de vida. Es más sabio no grabar mucho el programa.
Nafis
Con el uso normal, los números dados para flash y EEPROM no son ningún problema. La ecuación cambia cuando comienza a usarla como si usara SRAM.
jippie

Respuestas:

4

Solo los datos de la función se almacenan en la pila; Su código permanece en flash. En realidad, no puede reducir el uso de SRAM utilizando EEPROM porque, como ha visto, EEPROM no es direccionable de la misma manera. El código para leer y almacenar EEPROM también necesita usar algo de SRAM, ¡probablemente tanta SRAM como intentaba guardar! La EEPROM también es lenta para escribir y tiene una vida útil limitada (en número de escrituras en cada byte), lo que hace que sea poco práctico usarla para almacenar el tipo de datos temporales que usualmente guardamos en la pila. Es más adecuado para guardar datos que cambian con poca frecuencia, como la configuración de dispositivo única para dispositivos producidos en masa, o capturar errores poco frecuentes para su posterior análisis.

Editado: no hay una pila para esa función hasta que se ha llamado a la función, por lo que sí, es cuando se coloca cualquiera de los datos de la función. Lo que sucede después de que la función regresa es que su marco de pila (su área reservada de SRAM) ya no está reservada. Eventualmente será reutilizado por otra llamada de función. Aquí hay un diagrama de una pila C en memoria. Cuando un marco de pila ya no es útil, simplemente se libera y su memoria está disponible para ser reutilizada.

JRobert
fuente
Estoy pensando de esta manera, cuando se llama a la función, solo entonces los datos dentro de ella se almacenan en la pila. Después de la ejecución de la función, los datos se borran de la pila / SRAM. Estoy en lo cierto?
Nafis
5

Las variables locales y los parámetros de función se almacenan en la pila. Sin embargo, esa no es una razón para no usarlos. Las computadoras están diseñadas para funcionar de esa manera.

La memoria de pila solo se usa mientras una función está activa. Tan pronto como la función regresa, la memoria se libera. La memoria de pila es una BUENA cosa.

No desea utilizar funciones recursivas con muchos niveles de recursión, ni asignar muchas estructuras grandes en la pila. Sin embargo, el uso normal está bien.

La pila 6502 tiene solo 256 bytes, pero la Apple II funciona bien.

Duncan C
fuente
Entonces, ¿quiere decir que la función se guardará con todas sus variables locales, parámetros y expresiones en la pila temporalmente, solo cuando se llama? De lo contrario, permanecerá en el programa / memoria flash? Después de la ejecución, ¿se borrará de la pila? Estaba hablando de Arduino en realidad, ya que es Arduino Forum, no lo mencioné.
Nafis
No, solo los parámetros de la función y las variables locales están en la pila. El código de la función no se guarda en la pila. No pienses demasiado en esto.
Duncan C
5

El AVR (la familia de microcontroladores que se usa tradicionalmente en las placas Arduino) es una arquitectura de Harvard , lo que significa que el código ejecutable y las variables están en dos memorias separadas, en este caso flash y SRAM. El código ejecutable nunca deja memoria flash.

Cuando llama a una función, la dirección de retorno generalmente se transfiere a la pila; la excepción es cuando la llamada a la función ocurre al final de la función de llamada. En este caso, se utilizará la dirección de retorno de la función que llamó a la función de llamada; ya está en la pila.
Si cualquier otro dato se coloca en la pila depende de la presión de registro en la función de llamada y en la función llamada. Los registros son el área de trabajo de la CPU, el AVR tiene 32 registros de 1 byte. Se puede acceder directamente a los registros mediante instrucciones de la CPU, mientras que los datos en SRAM primero deberán almacenarse en los registros. Solo si los argumentos o la variable local son demasiado grandes o demasiados para caber en los registros, se colocarán en la pila. Sin embargo, las estructuras siempre se almacenan en la pila.

Puede leer los detalles de cómo el compilador GCC utiliza la pila en la plataforma AVR aquí: https://gcc.gnu.org/wiki/avr-gcc#Frame_Layout
Lea las secciones "Diseño de marco" y "Convención de llamadas" .

usuario2973
fuente
1

Inmediatamente después de una llamada a la función que ingresa a la función, el primer código que se ejecuta es disminuir el stackpointer en una cantidad igual al espacio requerido para las variables temporales internas de la función. Lo bueno de esto es que, por lo tanto, todas las funciones se vuelven reentrantes y recursivas, porque sus variables se construyen en la pila del programa que realiza la llamada. Eso significa que si una interrupción detiene la ejecución de un programa y transfiere la ejecución a otro, también puede llamar a la misma función sin que interfieran entre sí.

Paul Dent
fuente
1

He estado tratando de hacer un código de ejemplo para demostrar lo que dicen las excelentes respuestas aquí, sin éxito hasta ahora. La razón es que el compilador optimiza agresivamente las cosas. Hasta ahora, mis pruebas no han utilizado la pila en absoluto, incluso con variables locales en una función. Los motivos son:


  • El compilador puede en línea la llamada a la función, por lo tanto, la dirección de retorno podría no insertarse en la pila. Ejemplo:

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

    El compilador convierte eso en:

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

    Sin llamada de función, sin pila utilizada.


  • El compilador puede pasar argumentos en los registros , lo que le ahorra tener que empujarlos a la pila. Ejemplo:

    digitalWrite (13, 1);

    Compila en:

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

    Los argumentos se colocan en registros y, por lo tanto, no se utiliza ninguna pila (aparte de la dirección de retorno para llamar a digitalWrite).


  • Las variables locales bien se pueden poner en registros, de nuevo ahorrando tener que usar RAM. Esto no solo ahorra RAM sino que es más rápido.

  • El compilador optimiza las variables que no usa. Ejemplo:

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

    Ahora eso tiene que asignar 400 bytes para "barra", ¿no? No:

    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

    ¡El compilador optimizó toda la matriz ! Puede decir que realmente solo estamos haciendo una digitalWrite (9, 3)y eso es lo que genera.


Moraleja de la historia: no intentes dejar de pensar en el compilador.

Nick Gammon
fuente
La mayoría de las funciones no triviales usan la pila para guardar algunos registros, de modo que se puedan usar para contener variables locales. Luego tenemos esta situación divertida en la que el marco de la pila de la función contiene variables locales que pertenecen a su llamador .
Edgar Bonet