Come viene utilizzata la memoria dello stack per funzioni e variabili locali?


8

Volevo salvare alcuni valori nella EEPROM e volevo anche liberare SRAM evitando alcune dichiarazioni variabili, ma la memoria EEPROM è saggia.

Se voglio memorizzare un valore int, devo usare alcune espressioni ripetutamente. Ho pensato di fare alcune funzioni per quelli. Ma sono preoccupato che, se creo una funzione, occuperebbe ancora la memoria SRAM, meglio dichiarare una variabile int invece di usare EEPROM.

Come vengono memorizzate le funzioni e le variabili locali in SRAM? Memorizza solo l'indirizzo del puntatore di fuction dalla memoria flash o tutte le variabili e i comandi sono memorizzati nello stack?


4
Ricorda che EEPROM è scrivibile solo per un numero limitato di volte, la lettura è illimitata. Secondo la scheda tecnica AVR EEPROM ha solo 100000 cicli, il che suona molto ma quando si tenta di usarlo come SRAM, durerà solo un periodo abbastanza breve.
jippie,

OH MIO DIO! Successivamente, la EEPROM sarà inutile? Ho intenzione di controllare la scheda tecnica!
Nafis,

La memoria Flash ha anche un stile di vita. È più saggio non masterizzare molto il programma.
Nafis,

Con l'uso normale i numeri indicati per flash ed EEPROM non sono affatto un problema. L'equazione cambia quando inizi a usarla come se usassi SRAM.
jippie,

Risposte:


4

Solo i dati della funzione sono memorizzati nello stack; il suo codice rimane in flash. Non puoi davvero ridurre l'uso della SRAM usando EEPROM perché, come hai visto, EEPROM non è indirizzabile allo stesso modo. Il codice per leggere e memorizzare EEPROM deve anche usare un po 'di SRAM - probabilmente tanto SRAM quanto stavi cercando di salvare! Anche la EEPROM è lenta nella scrittura e ha una durata limitata (in numero di scritture per ogni byte), che rendono entrambi poco pratico l'utilizzo per la memorizzazione del tipo di dati temporanei che normalmente mettiamo in pila. È più adatto per il salvataggio di dati modificati di rado, come l'esclusiva configurazione del dispositivo per dispositivi prodotti in serie, o per l'acquisizione di errori rari per successive analisi.

Modificato: non c'è stack per quella funzione fino a quando la funzione non è stata chiamata, quindi sì, cioè quando viene inserito uno dei dati della funzione. Quello che succede dopo che la funzione ritorna è che il suo stack-frame (la sua area riservata di SRAM) non è più riservato. Alla fine verrà riutilizzato da un'altra chiamata di funzione. Ecco un diagramma di una pila C in memoria. Quando uno stack frame non è più utile, viene semplicemente rilasciato e la sua memoria diventa disponibile per essere riutilizzata.


Sto pensando in questo modo, quando viene chiamata la funzione, solo allora i dati al suo interno vengono archiviati nello stack. Dopo l'esecuzione della funzione, i dati vengono cancellati dallo stack / SRAM. Ho ragione?
Nafis,

5

Le variabili locali e i parametri di funzione sono memorizzati nello stack. Tuttavia, questo non è un motivo per non usarli. I computer sono progettati per funzionare in questo modo.

La memoria dello stack viene utilizzata solo quando una funzione è attiva. Non appena la funzione ritorna, la memoria viene liberata. La memoria dello stack è una buona cosa.

Non si desidera utilizzare funzioni ricorsive con molti livelli di ricorsione o allocare molte grandi strutture nello stack. L'uso normale va bene comunque.

Lo stack 6502 ha solo 256 byte, ma l'Apple II funziona perfettamente.


Quindi, vuoi dire che la funzione verrà salvata temporaneamente con tutte le sue variabili locali, i parametri e le espressioni nello stack, solo quando viene chiamata? Altrimenti rimarrà nel programma / nella memoria flash? Dopo l'esecuzione, verrà cancellato dallo stack? In realtà stavo parlando di Arduino, dato che è Arduino Forum, non ho menzionato questo.
Nafis,

No, solo i parametri della funzione e le variabili locali sono nello stack. Il codice della funzione non viene salvato nello stack. Non pensarci troppo.
Duncan C

5

L'AVR (la famiglia di microcontrollori utilizzata tradizionalmente sulle schede Arduino) è un'architettura di Harvard , il che significa che il codice eseguibile e le variabili si trovano in due memorie separate, in questo caso flash e SRAM. Il codice eseguibile non lascia mai la memoria flash.

Quando si chiama una funzione, l'indirizzo di ritorno viene di solito inserito nello stack - l'eccezione è quando la chiamata di funzione avviene alla fine della funzione di chiamata. In questo caso verrà invece utilizzato l'indirizzo di ritorno della funzione che ha chiamato la funzione chiamante: è già nello stack.
L'eventuale inserimento nello stack di altri dati dipende dalla pressione del registro nella funzione chiamante e nella funzione chiamata. I registri sono l'area di lavoro della CPU, l'AVR ha 32 registri da 1 byte. È possibile accedere direttamente ai registri tramite le istruzioni della CPU, mentre i dati nella SRAM dovranno prima essere memorizzati nei registri. Solo se gli argomenti o le variabili locali sono troppo grandi o troppi per essere inseriti nei registri, verranno messi in pila. Tuttavia, le strutture vengono sempre archiviate nello stack.

Puoi leggere i dettagli di come lo stack viene utilizzato dal compilatore GCC sulla piattaforma AVR qui: https://gcc.gnu.org/wiki/avr-gcc#Frame_Layout
Leggi le sezioni "Layout della cornice" e "Convenzione di chiamata" .


1

Immediatamente dopo una chiamata di funzione che entra nella funzione, il primo codice che viene eseguito è di decrementare lo stackpointer di un importo pari allo spazio richiesto per le variabili temporanee interne alla funzione. La cosa geniale di questo è che tutte le funzioni diventano quindi rientranti e ricorsive, perché le loro variabili sono costruite sullo stack del programma chiamante. Ciò significa che se un interrupt interrompe l'esecuzione di un programma e trasferisce l'esecuzione a un altro, anche questo può chiamare la stessa funzione senza che interferiscano l'uno con l'altro.


1

Ho cercato abbastanza duramente di fare un esempio di codice per dimostrare ciò che le eccellenti risposte qui dicono, senza successo finora. Il motivo è che il compilatore ottimizza le cose in modo aggressivo. Finora i miei test non hanno usato affatto lo stack, anche con variabili locali in una funzione. Le ragioni sono:


  • Il compilatore può in linea la chiamata di funzione, quindi l'indirizzo di ritorno potrebbe non essere inserito nello stack affatto. Esempio:

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

    Il compilatore lo trasforma in:

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

    Nessuna chiamata di funzione, nessuno stack utilizzato.


  • Il compilatore può passare argomenti nei registri , risparmiando così di doverli spingere nello stack. Esempio:

    digitalWrite (13, 1);

    Compila in:

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

    Gli argomenti vengono inseriti nei registri e quindi non viene utilizzato alcun stack (a parte l'indirizzo di ritorno per la chiamata a digitalWrite).


  • Le variabili locali possono essere inserite nei registri, risparmiando di nuovo di dover usare la RAM. Questo non solo consente di risparmiare RAM, ma è più veloce.

  • Il compilatore ottimizza le variabili non utilizzate. Esempio:

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

    Ora che ha ottenuto di destinare 400 byte per "bar" non è vero? 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

    Il compilatore ha ottimizzato l' intero array ! Può dire che stiamo davvero facendo un digitalWrite (9, 3)e questo è ciò che genera.


Morale della storia: non cercare di pensare al compilatore.


La maggior parte delle funzioni non banali utilizza lo stack per salvare alcuni registri, in modo che possano essere utilizzati per contenere variabili locali. Quindi abbiamo questa situazione divertente in cui il frame dello stack della funzione contiene variabili locali appartenenti al suo chiamante .
Edgar Bonet,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.