Hi, ich versteh den Begriff "Stack" in Zusammenhang mit den Cortex-M Controllern offenbar noch nicht und ich hab's über die SuFu nicht rausgefunden. Für einen 8051 ist der Stack nichts weiter als ein Speicher, auf dem Rücksprungadressen und Registerinhalte gesichert werden, beispielsweise beim Funktionsaufruf und in Interrupt-Service-Routinen. Der Stack im Cortex erfüllt die gleiche Funktion, im Forum hab ich aber auch gelesen, dass lokale Variablen von Funktionen ebenfalls auch auf dem Stack abgelegt werden (ich nehme mal an, dass das nicht GCC spezifisch ist, deswegen poste ich es nicht unter GCC), und globale Variablen ganz normal im SRAM. Dynamisches Speichermanagement verwendet den Heap, welcher für nichts anderes verwendet wird. Ist das soweit korrekt? Wie funktioniert denn jetzt das mit den lokalen Variablen, wenn die auf dem Stack abgebildet werden? Auf einem Stack sollen doch Werte gesichert werden, d.h. die verändern sich nicht bzw. sollen es nicht. Oder kann es sein, dass mit "Stack" gar nicht der Hardware-Stack gemeint ist, sondern ein dedizierter, vom Linker in der Größe berechneter Speicherbereich? Ralf
Der Stack liegt im S-Ram. Es gibt keine HW-Stack. Was meinst du mit "lokalen" Variablen? Globale Vairablen liegen im normalen SRam Wenn eine Funktion aufgerufen wird, werden diese im Stack gehalten bis die Funktion beendet wird. Danach werden die Variablen vom Stack wieder gelöscht. Der Heap ist wie du schon schreibst für dynamischen Speicher. Kritisch wird es, wenn der Stack nicht mehr ausreicht oder auf den Heap prallt. Dann passieren unschöne Dinge.
Hallo Tilo, danke für deine Antwort. > Der Stack liegt im S-Ram. Es gibt keine HW-Stack. Das ist klar, so ist es auch beim 8051 (ich kenne keine Architektur, die einen dedizierten Stack hat). Mit Hardware-Stack meinte ich eben den für Rücksprungadressen etc. verwendeten Bereich. > Was meinst du mit "lokalen" Variablen? Variablen, welche innerhalb einer Funktion deklariert sind. > Globale Vairablen liegen im normalen SRam Auch klar (wo sonst? :) ) > Wenn eine Funktion aufgerufen wird, werden diese im Stack gehalten bis > die Funktion beendet wird. Danach werden die Variablen vom Stack wieder > gelöscht. Und wie wird mit den Variablen auf dem Stack gearbeitet, wenn diese auf dem Stack sind? Wie ich geschrieben habe, ist für mich der Stack ein Bereich, auf dem Registerinhalte, etc. gesichert werden und somit eben nicht verändert werden sollen. Wo ist mein Verständnisproblem? > Kritisch wird es, wenn der Stack nicht mehr ausreicht oder auf den Heap > prallt. Dann passieren unschöne Dinge. Ebenfalls klar. Ralf
Der 8051 ist anders aufgebaut, als der ARM. Der 8051 kann vieles direkt im RAM ausführen, daher kommt man besser, wenn lokale Variablen überlagert werden. Prinzipiell kann man aber auch Variablen im Stack anlegen und über @R0,1 drauf zugreifen. Man muß dann aber jedesmal R0,1 umladen, da er kein Displacement kennt. Der ARM hingegen kann nichts im RAM machen, er muß alles erst in Register holen. Ein Überlagern hätte also keinen Vorteil, aber den Nachteil, daß Funktionen nicht reentrant sind.
Die PIC-Architektur (PIC16F88) von Microchip hat einen Hardwarestack. Kann maximal 8 Adressen speichern.
Tilo Lutz schrieb: > Der Stack liegt im S-Ram. Es gibt keine HW-Stack. Toll. Der Blinde erzählt dem Lahmen von Farben %-/ > Was meinst du mit "lokalen" Variablen? Variablen der Storageklasse "auto". Die der Compiler bei gebräuchlichen Plattformen auf den Stack legt. Ralf schrieb: > Für einen 8051 ist der Stack nichts weiter als ein Speicher, auf dem > Rücksprungadressen und Registerinhalte gesichert werden, beispielsweise > beim Funktionsaufruf und in Interrupt-Service-Routinen. Alle µC die über genug SRAM verfügen (oder externen RAM anbinden können), legen den Stack in diesen RAM. Nur sehr kleine Typen haben einen Hardwarestack, der i.d.R. auch sehr begrenzt ist. Ein Beispiel wäre der ATtiny12. Wenn man genug Platz auf dem Stack hat, kann man außer Returnadressen für ISR und Funktionsaufrufe auch andere Daten auf den Stack legen. Insbesondere ist es praktisch, die lokalen Variablen einer Funktion auf dem Stack anzulegen. So ähnlich, wie man in Assembler den Stack verwendet um Register per PUSH auf den Stack zu sichern (und dann die Register für lokale Variablen zu verwenden). I.d.R. wird dazu im Prolog der Funktion ein sogenannter Stackframe erzeugt, also einfach der Stackpointer so weit vorgerückt, daß der notwendige Platz reserviert ist. Wenn die Funktion verlassen wird, wird das wieder rückgängig gemacht. Auf dem gleichen Weg kann man natürlich auch Argumente an die Funktion übergeben bzw. Ergebnisse zurückgeben. Vorteil: funktioniert auch für Rekursion und die Speicherverwaltung ist pippileicht. Es gibt auch Architekturen, die von Haus aus mehrere Stacks erlauben, z.B. in Form mehrerer Stackpointer. Da bietet es sich dann an, einen separaten Daten- und Return-Stack zu bauen. XL
@Peter Danneger: > Der 8051 ist anders aufgebaut, als der ARM. Ja, ich versuche gerade "umzusteigen", nur sollte ich wohl meine Erfahrung mit dem 8051 "vergessen", anstatt zu versuchen, sie "mit ins Boot zu nehmen" :) > Der 8051 kann vieles direkt im RAM ausführen, daher kommt man besser, > wenn lokale Variablen überlagert werden. Wenn man das richtig macht, dann kann man selbst mit "nur" 256 Byte RAM, welche auch noch die Registerbänke, Stack, etc. beinhalten schon recht große Applikationen machen :) > Prinzipiell kann man aber auch Variablen im Stack anlegen und über @R0,1 > drauf zugreifen. Man muß dann aber jedesmal R0,1 umladen, da er kein > Displacement kennt. Das ist auf nem 8051 aber ineffizient (wissen wir wohl beide :) ). > Der ARM hingegen kann nichts im RAM machen, er muß alles erst in > Register holen. Ein Überlagern hätte also keinen Vorteil, aber den > Nachteil, daß Funktionen nicht reentrant sind. Kann er nicht? Ich muss glaub ich weiter vorn anfangen und mal das InstructionSet genauer unter die Lupe nehmen. @ Marco S.: > Die PIC-Architektur (PIC16F88) von Microchip hat einen Hardwarestack. > Kann maximal 8 Adressen speichern. Wieder was dazu gelernt :) @Axel: > Wenn man genug Platz auf dem Stack hat, kann man außer Returnadressen > für ISR und Funktionsaufrufe auch andere Daten auf den Stack legen. > Insbesondere ist es praktisch, die lokalen Variablen einer Funktion auf > dem Stack anzulegen. So ähnlich, wie man in Assembler den Stack > verwendet um Register per PUSH auf den Stack zu sichern (und dann die > Register für lokale Variablen zu verwenden). I.d.R. wird dazu im Prolog > der Funktion ein sogenannter Stackframe erzeugt, also einfach der > Stackpointer so weit vorgerückt, daß der notwendige Platz reserviert > ist. Wenn die Funktion verlassen wird, wird das wieder rückgängig > gemacht. Auf dem gleichen Weg kann man natürlich auch Argumente an die > Funktion übergeben bzw. Ergebnisse zurückgeben. Dann bin ich jetzt komplett verwirrt. Eins nach dem Anderen... Du schreibst, dass die Register für lokale Variablen verwendet werden (hat Peter ja auch geschrieben, weil der Cortex-M wohl nicht direkt auf dem RAM arbeiten kann). Abgesehen davon, dass das m.V.n. ja dann auch für globale Variablen gelten muss, verstehe ich aber immer noch nicht, wie dann mit den Variablen auf dem Stack gearbeitet wird. Ich glaub ich muss das mal an einem Beispiel versuchen zu erklären, wo ich die Blockade habe: Sagen wir eine Funktion hat drei lokale Variablen. Ich rufe die Funktion auf. Was passiert dann? Wenn es heisst "die lokalen Variablen werden auf dem Stack erzeugt", dann verstehe ich darunter simpel ausgedrückt drei PUSH-Instruktionen. Und jetzt? Die Dinger sind auf dem Stack, der für mich immer noch eine Sicherung bestehender Werte von RAM bzw. Registern ist. Wie kann dann beispielsweise mit der ersten der drei Variablen gearbeitet werden, wenn sie an dritter Position im Stack liegt (unter der Annahme dass die erste Variable auch die erste ist, die auf dem Stack gelandet ist)? Von einem Stack kann man doch nur die zuletzt auf dem Stack gesicherte Variable holen, aber sie dort doch nicht verändern, geschweige denn die Variablen, die tiefer im Stack liegen. Versteht ihr jetzt meine Denkblockade? > Es gibt auch Architekturen, die von Haus aus mehrere Stacks erlauben, > z.B. in Form mehrerer Stackpointer. Da bietet es sich dann an, einen > separaten Daten- und Return-Stack zu bauen. Okay, der Cortex-M hat ja zwei Stackpointer, aber die sind nicht getrennt als Daten- und Return-Stack zu verwenden. Ralf
Ralf schrieb: > Von einem Stack kann man doch nur die zuletzt auf > dem Stack gesicherte Variable holen, aber sie dort doch nicht > verändern, geschweige denn die Variablen, die tiefer im Stack liegen. Der Stack ist doch auch nur normales RAM und hat damit auch für jede Variabel eine Addresse. Vergiss das mal mit dem Push und Pop Befehlen. Man setzt einen Indexpointer auf die Variablen im Stack und addresiert sie mit Displacment und schon kommt man dran. Die Variablen legt man auf dem Stack an in dem man den Stackpointer manipuliert also vor dem Eintritt in die Funktion den Stackpointer um die Grösse der Variabeln subtraiert und beim verlassen dementsprechend den SP addiert.
Ralf schrieb: > Von einem Stack kann man doch nur die zuletzt auf > dem Stack gesicherte Variable holen, aber sie dort doch nicht > verändern, geschweige denn die Variablen, die tiefer im Stack liegen. > Versteht ihr jetzt meine Denkblockade? Schau dir mal die Speicherzugriffe/Adressierung "Load and Store with immediate offset" an. Dabei nutzt du ein Register (in diesem Fall den Stackpointer) zum Adressieren des SRAMs. Und der immediate Offset legt fest, ob du auf die dritte oder zweite Variable "vor der Rücksprungadresse" zugreifst. Könnte man nur per Push und Pop auf den Stack zugreifen, käme man tatsächlich nicht vernünftig an die tiefer im Stack liegenden Variablen ran.
@Helmut: > Der Stack ist doch auch nur normales RAM und hat damit auch für jede > Variable eine Addresse. Ja, das ist klar. > Vergiss das mal mit dem Push und Pop Befehlen. Werd ich wohl müssen :) > Man setzt einen Indexpointer auf die Variablen im Stack und addresiert > sie mit Displacment und schon kommt man dran. Die Variablen legt man auf > dem Stack an in dem man den Stackpointer manipuliert also vor dem > Eintritt in die Funktion den Stackpointer um die Grösse der Variabeln > subtraiert und beim verlassen dementsprechend den SP addiert. Mmmmh... ich glaube das erste Aufglimmen von Verständnis kommt. Mal schauen ob wir ein Licht draus machen können: - man (bzw.der Compiler) berechnet den Platzbedarf der Variablen und verschiebt den Stackpointer entsprechend (damit zwischendurch auftretende Interrupts oder aufgerufene Funktion die Variablen nicht zerstören und der Stack sauber arbeitet - der "frei" gewordene Stack-Speicher bildet die Variablen ab. Zugegriffen wird dann über einen zum Stackpointer relativen Pointer? Oder wie wird der von dir beschriebene "Indexpointer" gebildet? Er muss ja dann zum Stackpointer relativ sein, sonst können die Funktionen nicht reentrant sein, korrekt? - Die entsprechenden Werte werden dann in die Register geholt, dort nach Bedarf manipuliert (weil keine direkten Operationen auf dem RAM möglich sind) und wieder zurückgeschrieben, richtig? Wie nahe kommt das der Wahrheit? :) Ralf
Edit: @Helmut: Achim hat mir m.E. meinen Post gerade bestätigt. @Achim: > Schau dir mal die Speicherzugriffe/Adressierung "Load and Store with > immediate offset" an. Dabei nutzt du ein Register (in diesem Fall den > Stackpointer) zum Adressieren des SRAMs. Und der immediate Offset legt > fest, ob du auf die dritte oder zweite Variable "vor der > Rücksprungadresse" zugreifst. Vielen Dank, das deckt sich mit meiner Antwort an Helmut. Und wie ich gesagt habe, ich muss wirklich mal das InstructionSet herholen. > Könnte man nur per Push und Pop auf den Stack zugreifen, käme man > tatsächlich nicht vernünftig an die tiefer im Stack liegenden Variablen > ran. Ja, eben, das war dann auch meine o.g. Blockade! Aber jetzt wird die Sache viiiieeeel klarer :) Vielen Dank an alle. Ralf
> - der "frei" gewordene Stack-Speicher bildet die Variablen ab. > Zugegriffen wird dann über einen zum Stackpointer relativen Pointer? > Oder wie wird der von dir beschriebene "Indexpointer" gebildet? Er muss > ja dann zum Stackpointer relativ sein, sonst können die Funktionen nicht > reentrant sein, korrekt? Jetzt hast du es. Eigentlich ganz einfach. Die lokalen Variablen haben keine ständige fixe Adresse, sondern für sie wird Speicher 'reserviert' wenn die Funktion aufgerufen wird und der Speicher wird wieder freigegeben, wenn die Funktion den Return macht. Und was bietet sich da besseres an, als den Stack mitzubenutzen, auf dem man sich unaufwändig und geordnet ein paar Bytes nach Bedarf reservieren kann, indem man den Stackpointer einfach ein paar Bytes weiter runter bzw. raufzählt.
Axel Schwenke schrieb: > Vorteil: funktioniert auch für Rekursion Ist das für C nicht Pflicht? MfG Klaus
Diese Technik, lokale Daten im Stack zu speichern, findet man bei wohl allen aktuellen Prozessor-Architekturen jenseits der 8-Bit Klasse und auch bei einigen 8-Bittern. Also auch bei PC-Prozessoren. Grundvoraussetzung ist aber, dass der Prozessor effizient relativ zu einem Register oder direkt dem Stackpointer adressieren kann. 8051 kann das nicht, weshalb C Compiler dafür anders arbeiten müssen, wenn der Code effizient sein soll. Du bist in dieser Hinsicht also von der Ausnahme (8051) zur Regel umgestiegen.
Ralf schrieb: > Zugegriffen wird dann über einen zum Stackpointer relativen Pointer? > Oder wie wird der von dir beschriebene "Indexpointer" gebildet? Das hängt vom Befehlssatz ab. Wenn man Stackpointer-relativ adressieren kann, braucht man nichts weiter, in den meisten Fällen verwendet man eines der Pointer-Register mit Offset, das beim Eintritt in das Unterprogramm auf den Wert des SP gesetzt wird. Da man so selbst in der Hand hat welches Register, muss man mit anderen Schreiberlingen an der gleichen Software darüber garnicht einig sein, trotzdem hat sich oft für einen bestimmten Prozessor ein bestimmtes Register eingebürgert. Was man natürlich wissen muss, ist das Muster, nach dem die lokalen Variablen gespeichert werden, bei der C-Konvention in der Reihenfolge in der sie aufgeführt werden, bei Pascal umgekehrt. Danach richtet sich dann der Offset bezüglich der Rücksprungaddresse. Die Frage, warum überhaupt: lokale Variablen gehören eben zum Unterprogramm, sie leben genauso lang; sie werden ab dem Sprung dorthin gebraucht und nach dem Rücksprung nicht mehr, man kann sie also einfach auf dem Stack verwalten ohne dass unpraktische Lücken im Speicher entstehen. Gruss Reinhard
Reinhard Kern schrieb: > Die Frage, warum überhaupt: lokale Variablen gehören eben zum > Unterprogramm, sie leben genauso lang; sie werden ab dem Sprung dorthin > gebraucht und nach dem Rücksprung nicht mehr, man kann sie also einfach > auf dem Stack verwalten ohne dass unpraktische Lücken im Speicher > entstehen. Das ist ein Grund. Es ist aber auch ungemein platzsparend. Einem x86 reicht meist 1 Offset-Byte aus, um lokale Daten relativ zum Stackpointer zu adressieren. Für statische Daten braucht er jedoch 4 Bytes. Ein ARM ist bei statischer Adressierung noch schlimmer dran, weil der eine feste 32-Bit Adresse erst irgendwie ins Register zaubern muss, bevor er die Daten selbst laden/speichern kann (Cortext-M3/4: 6-8 Bytes pro Adresse). Dazu kommt heutzutage, dass diese Methode dem Verhalten von Caches sehr entgegen kommt.
Den ARM möchte man nicht wirklich in Assembler programmieren, da RISC. Wenn Du z.B. beim 8051 schreibst: - DJNZ Variable, Zieladresse Sieht das gleiche beim ARM etwa so aus: - Lade R1 mit Variablen-Adresse Low-Word - Addiere zu R1 die Variablen-Adresse High-Word - Lade R2 über R1-Pointer mit der Variable - DEC R2 - Store R2 über R1-Pointer nach Variable - JNZ nach Ziel Lasse also besser den C-Compiler diesen ganzen umständlichen Wust an Code erzeugen. Und dann brauchst Du Dich auch nicht um den Stack zu kümmern.
Peter, jetzt überziehst du. Dem DJNZ des 8051 entspricht realistischerweise etwas wie subs r0,r0,#1 bne ... weil jeder nicht völlig bescheuerte Compiler solche Variablen erst garnicht in den Speicher legt, sondern ins Register. Es sei denn der Schleifenzähler ist eine globale/statische Variable - aber wer macht sowas schon. Insgesamt stimmt aber, dass Assembler-Programmierung für RISCs umständlicher ist, weil deutlich mehr Operationen in mehrere Befehle aufgeteilt werden müssen als bei CISCs.
A. K. schrieb: > Dem DJNZ des 8051 entspricht > realistischerweise etwas wie > subs r0,r0,#1 > bne ... Nö. Z.B. zähle ich damit im 10ms Interrupt, bis 1s rum ist. DJNZ ist in Interrupts besonders praktisch, da es keine Statusbits verändert. Ich wollte schon den allgemeinen Fall aufzeigen, daß die Variable im RAM liegt bzw. im Stackframe. Daß der Compiler vesucht, häufige lokale Variablen in Registern zu halten, ist der Sonderfall. Oft reichen die Register auch nicht aus, um alle lokalen Variablen in Registern zu halten.
OK, nachdem der allgemeine Mechanismus jetzt hoffentlich klar ist, noch ein bisschen Folklore. Der Begriff Stack Pointer ist ja wohl klar, ebenfalls haben wir geklärt daß lokale Variablen (und Argumente) in einem auf dem Stack reservierten Speicherbereich namens Stack Frame vorgehalten werden. Der direkt verwandte Begriff ist Frame Pointer. Das ist im Prinzip ein Zeiger, der den gerade aktiven Stack Frame referenziert (je nach Wachstumsrichtung des Stacks und vorhandenen Adressierungsmodi den Anfang oder das Ende). Objekte innerhalb des Stack Frames (aka lokale Variablen) werden dann relativ zum Frame Pointer adressiert. Hochsprachentaugliche CPUs haben dafür ein extra Register (oder gleich mehrere). Z.B. wird auf x86 dafür gerne das Buffer Pointer (BP) Register verwendet. Wenn man jetzt im Prolog einer Funktion den Frame Pointer per push auf den Stack schiebt, dann den Stack Pointer in den Frame Pointer kopiert und relativ zum Frame Pointer den Stack Frame aufbaut, bekommt man Reentranz, Garbage Collection und Pipapo quasi kostenlos. Der gcc-Schalter -f[no-]omit-frame-pointer sagt dem Compiler, ob er den Frame Pointer permanent in einem Register halten soll (praktisch für das Debuggen) oder ob er bei hoher Registerlast den Frame Pointer auch mal verwerfen (und bei Bedarf rekonstruieren) soll. PS: "frame pointer" bietet sich auch als Google-Stichwort an XL
Peter Dannegger schrieb: > Daß der Compiler vesucht, häufige lokale Variablen in Registern zu > halten, ist der Sonderfall. Wie bitte??? Wenn er das bei RISC (oder x86) nicht tut, dann Tonne auf, rein damit, Tonne zu. Registeroptimierung ist eine Kernaufgabe von Compilern. RISC Prozessoren ergeben nur in Verbindung damit überhaupt einen Sinn. Aber die Art eines Programms und die verwendete Programmiertechnik spielen dabei eine erhebliche Rolle. Kleine Controllerprogramme können stark von globalen Variablen geprägt sein, und da läuft es klar in deine Richtung. Bei grossen Programmen sollte man freilich nicht mit Myriaden globaler Skalarvariablen hantieren. Zudem kann ein Compiler ggf. auch globale Variablen übergangsweise in Register legen, wenn er sich sicher ist, dass niemand sonst mitmischt. Grosse Programme sind von register-relativer Adressierung geprägt, weil eher verwaltete Datenstrukturen als globale Skalare dominieren.
A. K. schrieb: > Wie bitte??? Wenn er das bei RISC (oder x86) nicht tut, dann > Tonne auf, > rein damit, > Tonne zu. Du erzählst Quatsch. Es ging hier doch um Variablen im Stack, d.h. wenn dem Compiler die Register eben nicht mehr reichen. Ein Compiler kann doch keine weiteren Register hervorzaubern. Die wenigsten Funktionen bestehen nur aus 3 Zeilen Code. Ein guter Compiler sollte abschätzen können, welche Variablen am häufigsten zugegriffen werden. Oftmals hat man auch mehrere Schleifen ineinander. Da wird der Compiler möglichst die innere Schleife in Registern ausführen. Der äußere Schleifenzähler kann durchaus im RAM landen, da ja viel seltener zugegriffen. A. K. schrieb: > Registeroptimierung ist eine Kernaufgabe von Compilern. Das wußte ja schon der olle Keil C51. Aber irgendwann sind die Register belegt und dann nimmt er SRAM. Er hat natürlich weniger Reserve, da nur 8 Bit. D.h. die 8 Register können nur 2 long Werte fassen.
Peter Dannegger schrieb: > Es ging hier doch um Variablen im Stack, d.h. wenn dem Compiler die > Register eben nicht mehr reichen. Das hatte ich in diesem Thread bisher nicht so empfunden. Mein Eindruck war vielmehr, dass lokale Variablen hier der Einfachheit halber mit Stack-Variablen gleichgesetzt wurden. > Ein Compiler kann doch keine weiteren Register hervorzaubern. In gewisser Weise schon. Ein guter Compiler wird nicht notwendigerweise eine Variable für die Dauer einer Funktion fest mit einem Register verknüpfen, sondern wird ggf. ein Register an verschiedenen Stellen der Funktion als "Cache" für verschiedene Variablen verwenden. Der Platz auf dem Stack wird dann zwar benötigt, spielt aber keine wesentliche Rolle in der Laufzeit. Es ist freilich auch klar, dass ein RISC Prozessor mehr Register benötigt, als beispielsweise ein Akkumulator-Architektur. Allerdings hat er deshalb auch mehr davon.
Ich hab selber nicht ARM Assembler gemacht, sondern nur das Listing angesehen. Wenn man mal ein einfaches Blinkprogramm mit Timerinterrupt nimmt, strotzt das nur so von Load/Store Overhead. Gut, IO-Zugriffe darf er ja nicht wegoptimieren. Aber ich hätte schon erwartet, daß der Compiler bei nem Schwung IO- oder SRAM-Zugriffe analysiert, welches wäre die beste Base-Adresse, um mit Displacement die meisten IO-Register zu erreichen. Die neueren Compilerversionen geben ja mit Structs die Base-Adresse vor, zu Lasten einer umständlicheren, langatmigen Schreibweise. Aber das auch nur für je ein IO-Modul. Ein Displacement könnte jedoch mit einem Base-Pointer durchaus mehrere benachbarte IO-Module zugreifen. Der Compiler sollte mir auch die ganzen Derivat spezifischen Sonderbefehle abnehmen (z.B. Aliasadressen für IO-Bits, Clear-Register), so daß ich alles in plain C hinschreiben darf und er optimiert das Derivat spezifisch.
A. K. schrieb: > Peter Dannegger schrieb: >> Es ging hier doch um Variablen im Stack, d.h. wenn dem Compiler die >> Register eben nicht mehr reichen. > > Das hatte ich in diesem Thread bisher nicht so empfunden. Mein Eindruck > war vielmehr, dass lokale Variablen hier der Einfachheit halber mit > Stack-Variablen gleichgesetzt wurden. Kein Wunder, ging es doch in diesem Thread vor dem Kaperversuch durch die Compilerversteherfraktion :P um die Verwendung des Stacks abseits reiner PUSH/POP, CALL/RET Sequenzen. Daß es effizienter ist, lokale Variablen und Funktionsargumente gleich und ausschließlich in Registern zu halten, bleibt davon natürlich unberührt. ---- Aber wenn wir ohnehin beim Abschweifen sind, erzähle ich noch von meiner ersten Begegnung mit Stackmanipulationen. Und zwar war das, als ich erstmals eine PRIMM (PRint IMMediate) Funktion in Assembler gesehen habe. Damals muß ich so 14 gewesen sein. Die Funktion kommt zur Anwendung, wenn man aus einem Assemblerprogramm heraus einen Text z.B. auf den Bildschirm ausgeben will. Das sieht dann so aus:
1 | CALL primm |
2 | DB "Hallo Welt!", 0 |
3 | ;hier gehts weiter |
Das Null-terminierte Stringliteral steht hier mitten im Code. Eine Implementierung für Z80 könnte z.B. so aussehen:
1 | primm: EX (SP), HL |
2 | pr1: LD A, (HL) |
3 | INC HL |
4 | OR A |
5 | JRZ pr2 |
6 | CALL char_out |
7 | JR pr1 |
8 | pr2: EX (SP), HL |
9 | RET |
Sollte selbsterklärend sein. Die Alternative wäre, alle Stringliterale zentral an einer Stelle abzulegen und dann bei der Ausgabe sowas zu schreiben
1 | LD HL, string_0815 |
2 | CALL print_asciiz |
Der Vorteil von PRIMM ist, daß man beim Lesen des Codes sofort sieht was passiert und sich nicht erst über die "string_0815" Marke zum String durchhangeln muß. XL
Peter Dannegger schrieb: > Wenn man mal ein einfaches Blinkprogramm mit Timerinterrupt nimmt, > strotzt das nur so von Load/Store Overhead. Was schätzt du: Wieviel Prozent eines normalen 100KB Controller-Programmes machen direkte I/O-Zugriffe aus? I/O-Befehle hat ARM nicht. Der Aufwand um ein I/O-Bit anzusprechen ist viel höher als bei 8051. 8051 wurde direkt dafür entwickelt, ARM nicht. Andererseits: Grössere Programme haben grössere Datenstrukturen. Arrays, Structs, verpointerter Kram, Hashtables, ... Nicht viel anders als bei PCs. Und da sind ARMs dann nicht so übel. Daten werden nicht hauptsächlich mit absoluter Adresse angesprochen, sondern meistens relativ zu einem Pointer. Die meisten solcher Daten sind dann auch nicht volatile, wodurch Optimierung möglich wird. Der Eindruck, dass ARM viel mehr Code braucht, der trügt. In kleinem Umfeld mit hohem Anteil von I/O-Zugriffen stimmt das noch, aber darüber kehrt sich das um. > Ein Displacement könnte jedoch mit einem Base-Pointer durchaus mehrere > benachbarte IO-Module zugreifen. Nein. ARM-Displacements adressieren +/-4095 Bytes, Thumb-v1 weniger. Da ein einzelnes I/O-Modul bei ARMs stets einen 4KB Adressraum einnimmt, ist es nie der Fall, dass eine Basisadresse sich für mehr als ein Modul eignet. Allerdings könnte der Compiler in der Funktion einmal eine Adresse innerhalb des Moduls laden und später alles darin über dieses Register adressieren. Daran hapert es etwas.
Axel Schwenke schrieb: > Das Null-terminierte Stringliteral steht hier mitten im Code. Ja, so habe ich es zu meinen 8051 Assemblerzeiten auch gemacht:
1 | ;-------------------------------------------------------------------------- |
2 | ; UP: Ausgabe des Textes im Flash nach Aufruf (inline) |
3 | ; ==================================================== |
4 | ; |
5 | lcd_text_inline: |
6 | pop dph ; hole Adresse des Textes |
7 | pop dpl |
8 | jmp _lcdti2 |
9 | _lcdti1: |
10 | call lcd_data ; Ausgabe eines Bytes ans LCD |
11 | _lcdti2: |
12 | clr a ; kein Offest |
13 | movc a, @a+dptr ; lese Byte aus Flash |
14 | inc dptr ; zeige auf naechstes Byte |
15 | jnz _lcdti1 ; ungleich 0 -> Anzeigen |
16 | jmp @a+dptr ; springe zurueck hinter das 0-Byte |
17 | ;-------------------------------------------------------------------------- |
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.