Forum: Mikrocontroller und Digitale Elektronik Stack und rcalls


von Wishmaster (Gast)


Lesenswert?

Hi,

ich weiss nicht ob dieses problem vorher schon einmal behandelt wurde
aber ich habe folgende idee mit dem stack und rcall zu arbeiten:

Vorerst ein Term wie er unter Windows aussehen würde...

z.b C Syntax:

call xyz ( arg1, arg2, arg3, arg4, arg5, .... )

Windows Assembler:

push argx
push arg...
push arg5
push arg4
push arg3
push arg2
push arg1
call xyz

So idee ist nun folgende falls ich das richtig in tutorials gelesen
habe legt mir rcall die rücksprung adresse auf den stack was bedeutet
ich hätte folgendes Scenario :

push argx
push arg...
push arg5
push arg4
push arg3
push arg2
push arg1
rcall xyz

ich hätte also nach meinem "push arg1" anschliessend noch die
rücksprung adresse auf dem stack ganz oben liegen was ein wenig unschön
ist. Da ich ein etwas grösseres Projekt plane wo auch einige Funktionen
zustande kommen werden möchte ich oben beschriebenes System nutzen um
mir später auch keine grosse sorgen machen zu müssen über eventuell
freie register. Nun meine frage hat vielleicht einer von euch dazu
schon einmal eine elegante Lösung gefunden ?

Eine recht stupide idee wäre nun die rücksprung adresse vom stack zu
holen irgendwo zu sichern und am ende meines calls(funktion) wieder auf
den Stack zu legen. Allerdings frage ich mich ob das wirklich eine
effektive lösung ist.

Für ideen oder anregungen bzw erfahrungen hier wäre ich dankbar.

Danke schon mal im vorraus
Wishmaster

von Peter D. (peda)


Lesenswert?

"Allerdings frage ich mich ob das wirklich eine
effektive lösung ist."

Nein, ganz und garnicht !

Die übliche Methode ist die Übergabe in Registern.
Wozu sonst hat man denn die 32 Register, wenn nicht zum Benutzen.

Und bei großen Datenfeldern übergibt man einfach den Zeiger auf dessen
Anfang, z.B. im X-,Y- oder Z-Registerpaar.

Schau Dir einfach mal andere Programme an.


Peter

von Jörg Wunsch (Gast)


Lesenswert?

Für viele oder lange Argumente benutzen Compiler gelegentlich auch die
Übergabe via Stack.  In dem Fall ist es üblich, sich einen
Framepointer zu berechnen (rr28..29 bieten sich an) und danach die
Argumente als Offset zum Framepointer anzugeben.  (Negative Offsets
wären lokale Variablen innerhalb des aktuellen Stackframes.)

Aber das kann man natürlich dann gleich einen C-Compiler machen
lassen...

von Wishmaster (Gast)


Lesenswert?

Hi,

Sicherlich ist es effektiv wenn ich grosse Datenfelder(Arrays)
beispielsweise strings via einem Pointer übergebe das war auch in
meinen überlegungen es wäre unsinnig beispielsweise jeden buchstaben
einzeln zu pushen.

Peter:

Vielleicht gebe ich vorher eine kleine erklärung was ich so grob
vorhabe. Ich habe einen AVR128 mit einem FTDI chip und zusätzlich Sind
LEDs und Taster und ein LCD angeschlossen. Die kommunikation wird
später ausschliesslich über USB stattfinden worüber ich Funktionen auf
den AVR ansprechen kann. Alleine für diesen Datentransfer und
Auswertung werde ich einige register brauchen um die auswertung
effektiv ausführen zu können. Da nun aber weiterhin noch sachen wie
Taster, LEds und LCD mit einspielen so wie interne Algorithmen auf dem
AVR später möchte ich ungerne jedes gerade freie register im auge
behalten müssen daher die überlegung mit dem stack und einer
vernünftigen Kapeselung von Funktionen die ich dann später in
"librarys" ablegen kann. Auf diese art könnte ich z.b sagen
ich nehme mir sagen wir mal 5 feste register für die Funktionen und
hole mir meine argumente wie ich sie brauche. Ich wäre aber trotzdem
mal daran interessiert diese realisierung in einem Programm zu sehen
weisst du da zufällig ein nettes Beispiel ?


Jörg:

Die idee mit dem Framepointer gefällt mir. Sicherlich könnten wir jetzt
eine diskussion anfangen über C und Assembler ich habe mich allerdings
dafür entschieden in Assembler zu programmieren. Hast du eventuell zu
deiner Idee irgendeinen kleinen Beispielcode oder ist dir ein
bekanntest programm bekannt was diese technik nutzt ? Ich bin mir noch
nicht so ganz klar wie das zu realisieren ist.


Wishmaster

von Jörg Wunsch (Gast)


Lesenswert?

Nimm einfach mal einen C-Compiler (AVR-GCC in diesem Falle, die
anderen benutzen meines Wissens alle getrennte Daten- und
Return-Stacks) und laß Dir eine simple Funktion compilieren.

Grob gesagt: beim Aufruf der Funktion wird durch den Call die
Rückkehradresse auf den Stack gelegt.  Danach rettet man den alten
frame pointer, liest sich den stack pointer ein (CLI nicht
vergessen!), speichert ihn als neuen frame pointer ab und subtrahiert
vom stack pointer den Platz, den man für die lokalen Variablen
benötigt.  Danach kann man frame pointer mit positivem Offset (Y+n)
für die Argumentliste benutzen, mit negativem Offset (Y-n) für die
lokalen Variablen.

Beim Verlassen der Funktion dann umgekehrt: stack pointer wieder auf
den Wert des frame pointers rücksetzen (damit werden alle lokalen
Variablen ,,vernichtet''), pop framepointer, return.

von Peter D. (peda)


Lesenswert?

"möchte ich ungerne jedes gerade freie register im auge
behalten müssen"

Das macht man ja auch nicht.

Entweder Du machst es wie die Compiler, d.h es gibt grundsätzlich keine
"freien" Register. Variablen, die der Aufrufer noch danach wirklich
benötigt, muß er entweder pushen oder im SRAM anlegen.


Oder Du machst es codeoptimiert und definierst Dir 16 Register für
häufige globale Variablen und die anderen 16 Register sind dann die
nicht "freien" Arbeitsregister, die der Aufrufer je nach Bedarf
sichern muß.


Optimieren bedeutet, daß man Funktionen so schreibt, daß sie in 80..90%
aller Fälle optimal sind und nicht, daß sie in 80..90% aller Fälle nur
einen Haufen unnötigen Code enthalten.

Und in der Regel werden die Variablen, die man einer Funktion übergeben
hat, eben nicht mehr benötigt, sondern nur das Ergebnis daraus.
Deshalb hat es sich auch eingebürgert, daß man in den selben Registern,
die den ersten Operanden enthalten, auch das Ergebnis wieder zurück
liefert.


Anders gesagt, übertriebenes Pushen und Popen kostet dich nur unnötig
viel Rechenzeit und Codespeicher.
Und je mehr man pusht und popt, umso häufiger macht man Fehler dabei,
z.B. ungleiches Push/Pop oder Registervertauschung, d.h. Dein Code wird
fehleranfälliger.


Peter

von Jörg Wunsch (Gast)


Lesenswert?

> Entweder Du machst es wie die Compiler, d.h es gibt grundsätzlich
> keine "freien" Register. Variablen, die der Aufrufer noch danach
> wirklich benötigt, muß er entweder pushen oder im SRAM anlegen.

Ich kenne zwar nur den GCC, aber der macht es zumindest nicht so.  Er
hat eine Reihe von scratch registers, die auch zum Weiterreichen der
Parameter genutzt werden (erst wenn sie nicht mehr ausreichen, wird
der Stack genommen), und eine Reihe von Registern, die eine gerufene
Funktion nicht zerstören darf (d. h. wenn sie mehr Register braucht,
muß sie selbst push/pop'en).

Prinzipiell könnte man sich außerdem in den 14 nicht freien Registern
häufig benötigte globale Variablen ablegen.  Allerdings ist das für
ein größeres Projekt nur in den seltensten Fällen codeoptimal, da man
diese Register dann permanent dem Compiler entzieht, so daß er u. U.
auf SRAM für andere Dinge ausweichen muß, obwohl er dort mit noch
einem verfügbaren Register mehr (und einem push/pop pro
Funktionsaufruf) besseren Code erzeugen könnte.

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
Noch kein Account? Hier anmelden.