Wie wird der Stapelspeicher für Funktionen und lokale Variablen verwendet?

8

Ich wollte einige Werte im EEPROM speichern und wollte auch SRAM freigeben, indem ich einige Variablendeklarationen vermeide, aber der EEPROM-Speicher ist byteweise.

Wenn ich einen int-Wert speichern möchte, muss ich einige Ausdrücke wiederholt verwenden. Ich dachte, ich würde einige Funktionen für diese machen. Ich befürchte jedoch, dass beim Erstellen einer Funktion der SRAM-Speicher weiterhin belegt ist. Besser, ich deklariere eine int-Variable, anstatt das EEPROM zu verwenden.

Wie werden die Funktionen und die lokalen Variablen im SRAM gespeichert? Speichert es nur die Adresse des Funktionszeigers aus dem Flash-Speicher oder werden alle Variablen und Befehle auf dem Stapel gespeichert?

Nafis
quelle
4
Denken Sie daran, dass das EEPROM nur für eine begrenzte Anzahl von Malen beschreibbar ist und das Lesen unbegrenzt ist. Laut dem AVR-Datenblatt hat das EEPROM nur 100000 Zyklen, was sehr viel klingt, aber wenn Sie versuchen, es als SRAM zu verwenden, wird es nur eine relativ kurze Zeit dauern.
Jippie
OH MEIN GOTT! Wird das EEPROM danach unbrauchbar? Ich werde das Datenblatt überprüfen!
Nafis
Der Flash-Speicher hat auch einen Lebenszyklus. Es ist klüger, das Programm nicht viel zu brennen.
Nafis
Bei normaler Verwendung sind die für Flash und EEPROM angegebenen Nummern überhaupt kein Problem. Die Gleichung ändert sich, wenn Sie sie wie SRAM verwenden.
Jippie

Antworten:

4

Nur die Daten der Funktion werden auf dem Stapel gespeichert. Der Code bleibt im Flash. Sie können die SRAM-Nutzung nicht wirklich reduzieren, indem Sie stattdessen das EEPROM verwenden, da das EEPROM, wie Sie gesehen haben, nicht auf die gleiche Weise adressierbar ist. Der Code zum Lesen und Speichern des EEPROM muss auch einen SRAM verwenden - wahrscheinlich so viel SRAM, wie Sie speichern wollten! Das EEPROM ist auch langsam zu schreiben und hat eine begrenzte Lebensdauer (in Anzahl der Schreibvorgänge pro Byte). Beides macht es unpraktisch, die Art der temporären Daten zu speichern, die wir normalerweise auf den Stapel legen. Es eignet sich besser zum Speichern selten geänderter Daten, z. B. der einzigartigen Gerätekonfiguration für Geräte in Massenproduktion, oder zum Erfassen seltener Fehler für spätere Analysen.

Bearbeitet: Es gibt keinen Stapel für diese Funktion, bis die Funktion aufgerufen wurde. Ja, dann werden die Daten der Funktion dort abgelegt. Nach der Rückkehr der Funktion wird der Stapelrahmen (der reservierte Bereich des SRAM) nicht mehr reserviert. Es wird eventuell von einem anderen Funktionsaufruf wiederverwendet. Hier ist ein Diagramm eines C-Stapels im Speicher. Wenn ein Stapelrahmen nicht mehr nützlich ist, wird er einfach freigegeben und sein Speicher kann wieder verwendet werden.

JRobert
quelle
Ich denke so, wenn die Funktion aufgerufen wird, werden nur dann die darin enthaltenen Daten im Stapel gespeichert. Nach Ausführung der Funktion werden die Daten vom Stack / SRAM gelöscht. Habe ich recht?
Nafis
5

Lokale Variablen und Funktionsparameter werden auf dem Stapel gespeichert. Dies ist jedoch kein Grund, sie nicht zu verwenden. Computer sind so konzipiert, dass sie so funktionieren.

Der Stapelspeicher wird nur verwendet, wenn eine Funktion aktiv ist. Sobald die Funktion zurückkehrt, wird der Speicher freigegeben. Stapelspeicher ist eine gute Sache.

Sie möchten keine rekursiven Funktionen mit vielen Rekursionsebenen verwenden oder viele große Strukturen auf dem Stapel zuweisen. Der normale Gebrauch ist jedoch in Ordnung.

Der 6502-Stack ist nur 256 Byte groß, aber der Apple II funktioniert einwandfrei.

Duncan C.
quelle
Sie meinen also, die Funktion wird mit all ihren lokalen Variablen, Parametern und Ausdrücken nur dann vorübergehend im Stapel gespeichert, wenn sie aufgerufen wird? Sonst bleibt es im Programm- / Flash-Speicher? Wird es nach der Ausführung vom Stapel gelöscht? Ich habe tatsächlich über Arduino gesprochen, da es sich um das Arduino Forum handelt, habe ich das nicht erwähnt.
Nafis
Nein, nur die Parameter und lokalen Variablen der Funktion befinden sich auf dem Stapel. Der Code der Funktion wird nicht auf dem Stapel gespeichert. Überdenken Sie das nicht.
Duncan C
5

Der AVR (die Mikrocontroller-Familie, die traditionell auf Arduino-Boards verwendet wird) ist eine Harvard-Architektur , dh ausführbarer Code und ausführbare Variablen befinden sich in zwei separaten Speichern - in diesem Fall Flash und SRAM. Der ausführbare Code verlässt niemals den Flash-Speicher.

Wenn Sie eine Funktion aufrufen, wird die Rücksprungadresse normalerweise an den Stapel verschoben. Die Ausnahme ist, wenn der Funktionsaufruf am Ende der aufrufenden Funktion erfolgt. In diesem Fall wird stattdessen die Rücksprungadresse der Funktion verwendet, die die aufrufende Funktion aufgerufen hat - sie befindet sich bereits auf dem Stapel.
Ob andere Daten auf den Stapel gelegt werden, hängt vom Registerdruck in der aufrufenden Funktion und in der aufgerufenen Funktion ab. Register sind der Arbeitsbereich der CPU, der AVR verfügt über 32 1-Byte-Register. Auf die Register kann direkt über CPU-Anweisungen zugegriffen werden, während Daten im SRAM zuerst in Registern gespeichert werden müssen. Nur wenn Argumente oder lokale Variablen zu groß oder zu viele sind, um in Register zu passen, werden sie auf den Stapel gelegt. Strukturen werden jedoch immer auf dem Stapel gespeichert.

Einzelheiten zur Verwendung des Stacks durch den GCC-Compiler auf der AVR-Plattform finden Sie hier: https://gcc.gnu.org/wiki/avr-gcc#Frame_Layout
Lesen Sie die Abschnitte "Frame Layout" und "Calling Convention". .

user2973
quelle
1

Unmittelbar nach dem Aufrufen eines Funktionsaufrufs in die Funktion wird als erster Code der Stapelzeiger um einen Betrag dekrementiert, der dem Platz entspricht, der für temporäre Variablen innerhalb der Funktion erforderlich ist. Das Geniale daran ist, dass alle Funktionen daher wiedereintrittsfähig und rekursiv werden, da ihre Variablen auf dem Stapel des aufrufenden Programms basieren. Das heißt, wenn ein Interrupt die Ausführung eines Programms anhält und die Ausführung an ein anderes überträgt, kann auch er dieselbe Funktion aufrufen, ohne dass sie sich gegenseitig stören.

Paul Dent
quelle
1

Ich habe mich sehr bemüht, ein Beispiel für Code zu erstellen, um zu demonstrieren, was die hervorragenden Antworten hier aussagen, bisher ohne Erfolg. Der Grund ist, dass der Compiler die Dinge aggressiv optimiert. Bisher haben meine Tests den Stack überhaupt nicht verwendet, selbst mit lokalen Variablen in einer Funktion. Die Gründe sind:


  • Der Compiler kann in-line den Funktionsaufruf, so dass die Absenderadresse nicht geschoben überhaupt auf die Stapel werden könnte. Beispiel:

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

    Der Compiler macht daraus:

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

    Kein Funktionsaufruf, kein Stack verwendet.


  • Der Compiler kann Argumente in Registern übergeben , so dass er sie nicht auf den Stapel schieben muss. Beispiel:

    digitalWrite (13, 1);

    Kompiliert in:

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

    Die Argumente werden in Register eingetragen und somit wird kein Stapel verwendet (abgesehen von der Rücksprungadresse für den Aufruf von digitalWrite).


  • Lokale Variablen können durchaus in Register eingetragen werden, wodurch wiederum RAM benötigt wird. Dies spart nicht nur RAM, sondern ist auch schneller.

  • Der Compiler optimiert nicht verwendete Variablen. Beispiel:

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

    Nun , da die bekommen 400 Bytes zuzuteilen für „bar“ oder nicht? Nee:

    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

    Der Compiler hat das gesamte Array optimiert ! Es kann sagen, dass wir wirklich nur ein machen digitalWrite (9, 3)und das ist es, was es erzeugt.


Moral der Geschichte: Versuchen Sie nicht, den Compiler zu überdenken.

Nick Gammon
quelle
Die meisten nicht trivialen Funktionen verwenden den Stapel zum Speichern einiger Register, sodass diese zum Speichern lokaler Variablen verwendet werden können. Dann haben wir diese lustige Situation, in der der Stapelrahmen der Funktion lokale Variablen enthält, die zu ihrem Aufrufer gehören .
Edgar Bonet