Como a memória da pilha é usada para funções e variáveis ​​locais?

8

Eu queria salvar alguns valores na EEPROM e também queria liberar SRAM, evitando algumas declarações de variáveis, mas a memória da EEPROM é byte.

Se eu quiser armazenar um valor int, tenho que usar algumas expressões repetidamente. Eu pensei em fazer algumas funções para elas. Mas estou preocupado que, se eu criar uma função, ela ainda ocuparia memória SRAM, melhor eu declaro uma variável int em vez de usar EEPROM.

Como as funções e as variáveis ​​locais são armazenadas na SRAM? Ele armazena apenas o endereço do ponteiro de função da memória flash ou todas as variáveis ​​e comandos são armazenados na pilha?

Nafis
fonte
4
Lembre-se de que a EEPROM é gravável apenas por um número limitado de vezes, a leitura é ilimitada. De acordo com a folha de dados do AVR, a EEPROM possui apenas 100000 ciclos, o que soa muito, mas quando você tenta usá-la como SRAM, ela dura apenas um período bastante curto.
jippie
AMD! Depois disso, a EEPROM será inútil? Vou verificar a folha de dados!
Nafis
A memória Flash também possui um ciclo de vida. É mais sensato não gravar muito o programa.
Nafis
Em uso normal, os números indicados para flash e EEPROM não são problema. A equação muda quando você começa a usá-lo, como você usa SRAM.
jippie

Respostas:

4

Somente os dados da função são armazenados na pilha; seu código permanece em flash. Você não pode realmente reduzir o uso da SRAM usando a EEPROM, porque, como você viu, a EEPROM não é endereçável da mesma maneira. O código para ler e armazenar a EEPROM também precisa usar alguma SRAM - provavelmente a mesma quantidade que você estava tentando salvar! A EEPROM também é lenta na gravação e tem uma vida útil limitada (em número de gravações em cada byte), o que torna impraticável o uso para armazenar o tipo de dados temporários que geralmente colocamos na pilha. É mais adequado para salvar dados alterados com pouca frequência, como a configuração exclusiva de dispositivos produzidos em massa, ou capturar erros não frequentes para análises posteriores.

Editado: Não há pilha para essa função até que a função tenha sido chamada; portanto, sim, é quando qualquer dado da função é colocado lá. O que acontece após o retorno da função é que seu quadro de pilha (sua área reservada da SRAM) não está mais reservada. Eventualmente, será reutilizado por outra chamada de função. Aqui está um diagrama de uma pilha C na memória. Quando um quadro de pilha não é mais útil, ele é simplesmente liberado e sua memória fica disponível para reutilização.

JRobert
fonte
Estou pensando dessa maneira, quando a função é chamada, somente então os dados dentro dela são armazenados na pilha. Após a execução da função, os dados são apagados da pilha / SRAM. Estou certo?
Nafis
5

Variáveis ​​locais e parâmetros de função são armazenados na pilha. No entanto, esse não é um motivo para não usá-los. Os computadores são projetados para funcionar dessa maneira.

A memória da pilha está em uso apenas enquanto uma função está ativa. Assim que a função retornar, a memória será liberada. Pilha de memória é uma coisa boa.

Você não deseja usar funções recursivas com muitos níveis de recursão ou alocar muitas estruturas grandes na pilha. O uso normal é bom, no entanto.

A pilha 6502 tem apenas 256 bytes, mas o Apple II funciona bem.

Duncan C
fonte
Então, você quer dizer que a função será salva com todas as suas variáveis, parâmetros e expressões locais na pilha temporariamente, somente quando for chamada? Caso contrário, ele permanecerá no programa / memória flash? Após a execução, ele será apagado da pilha? Na verdade, eu estava falando sobre o Arduino, como é o Fórum do Arduino, não mencionei isso.
Nafis
Não, apenas os parâmetros e variáveis ​​locais da função estão na pilha. O código da função não é salvo na pilha. Não pense demais nisso.
Duncan C
5

O AVR (a família de microcontroladores tradicionalmente usada em placas Arduino) é uma arquitetura de Harvard , o que significa que código e variáveis ​​executáveis ​​estão em duas memórias separadas - neste caso, flash e SRAM. O código executável nunca sai da memória flash.

Quando você chama uma função, o endereço de retorno geralmente é enviado para a pilha - a exceção ocorre quando a chamada da função ocorre no final da função de chamada. Nesse caso, o endereço de retorno da função que chamou a função de chamada será usado - ele já está na pilha.
Se quaisquer outros dados são colocados na pilha depende da pressão do registro na função de chamada e na função chamada. Registradores são a área de trabalho da CPU, o AVR possui 32 registros de 1 byte. Os registradores podem ser acessados ​​diretamente pelas instruções da CPU, enquanto os dados na SRAM primeiro precisam ser armazenados nos registradores. Somente se os argumentos ou a variável local forem muito grandes ou muitos para caberem nos registros, eles serão colocados na pilha. No entanto, as estruturas são sempre armazenadas na pilha.

Você pode ler os detalhes de como a pilha é usada pelo compilador GCC na plataforma AVR aqui: https://gcc.gnu.org/wiki/avr-gcc#Frame_Layout
Leia as seções "Layout do quadro" e "Convenção de chamada" .

user2973
fonte
1

Imediatamente após uma chamada de função entrar na função, o primeiro código executado é diminuir o ponteiro da pilha em uma quantidade igual ao espaço necessário para variáveis ​​temporárias internas à função. A coisa brilhante sobre isso é que todas as funções se tornam re-entrantes e recursivas, porque suas variáveis ​​são construídas na pilha do programa de chamada. Isso significa que, se uma interrupção interrompe a execução de um programa e transfere a execução para outro, também pode chamar a mesma função sem que eles interfiram entre si.

Paul Dent
fonte
1

Eu tenho me esforçado bastante para criar um exemplo de código para demonstrar o que as excelentes respostas aqui estão dizendo, sem sucesso até agora. A razão é que o compilador otimiza agressivamente as coisas. Até agora, meus testes não usaram a pilha, mesmo com variáveis ​​locais em uma função. Os motivos são:


  • O compilador pode alinhar a chamada de função, portanto, o endereço de retorno pode não ser inserido na pilha. Exemplo:

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

    O compilador transforma isso em:

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

    Nenhuma chamada de função, nenhuma pilha usada.


  • O compilador pode passar argumentos nos registradores , poupando-os de enviá-los para a pilha. Exemplo:

    digitalWrite (13, 1);

    Compila em:

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

    Os argumentos são colocados em registradores e, portanto, nenhuma pilha é usada (além do endereço de retorno para chamar digitalWrite).


  • Variáveis ​​locais podem muito bem ser colocadas em registradores, novamente economizando a necessidade de usar RAM. Isso não apenas economiza RAM, mas é mais rápido.

  • O compilador otimiza as variáveis ​​que você não usa. Exemplo:

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

    Agora isso tem que alocar 400 bytes para "bar", não é? Não:

    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

    O compilador otimizou toda a matriz ! Pode dizer que realmente estamos apenas fazendo um digitalWrite (9, 3)e é isso que ele gera.


Moral da história: não tente pensar melhor no compilador.

Nick Gammon
fonte
A maioria das funções não triviais usa a pilha para salvar alguns registros, para que eles possam ser usados ​​para armazenar variáveis ​​locais. Então temos uma situação engraçada em que o quadro de pilha da função contém variáveis ​​locais pertencentes ao seu chamador .
Edgar Bonet