Einzelnen Beitrag anzeigen

Benutzerbild von sirius
sirius

Registriert seit: 3. Jan 2007
Ort: Dresden
3.443 Beiträge
 
Delphi 7 Enterprise
 
#15

Re: Wie verhält sich der Stack bei einem rekursiven Algorith

  Alt 25. Feb 2007, 20:30
@Matthias

Erstmal eine kleine Korrektur: Nicht das Betriebssystem organisiert den Stackpointer, sondern die CPU. Wie auch alle anderen Register. Nur wenn ich will, dann kann ich auch den Stackpointer einfach verändern. Ist ja ein normales Register. Dasselbe kann und macht natürlich dann auch das Betriebssystem.

Und das zweite was ich anmerken möchte: Das LIFO-Prinzip auf dem Stack ist richtig. Man sollte allerdings nicht vergessen, dass man auch den Stack frei adressieren und irgendwo darin lesen und schreiben kann. Und das wird normalerweise auch so genutzt, dafür ist ja der BasePointer (BP bzw. EBP)

Und ...ähm..., dass der Stack in Byte-Schritten bewegt wurde ist sehr lange her. War zumindest nicht zu meiner "aktiven Phase"


Wie sieht es nun mit der Übergabe von Werten an Funktionen und Prozeduren aus (und die Rückgabe von Funktionsergebnissen)?
Wenn du in ASM programmierst ist es dir vollkommen selbst überlassen, wie du das gestaltest. Ums mal extrem zu machen: Du kannst sie auch auf die Festplatte schreiben. Für gewöhnlich wird dies aber nicht so gemacht. Die meisten Programmiersprachen legen die Parameter auf den Stack, dabei kann die Reihenolge auch verschieden sein. In Delphi werden die ersten 3 Parameter meist in die Register EAX, EDX und ECX gelegt (je nachdem ob sie reinpassen). Wenn du das selber festlegen willst oder musst, schreibst du ja diese Aufrufkonventionen dazu (--> siehe "stdcall", "register", "cdecl"). Delphi nimmt wie gesagt, normalerweise Register, C nimmt cdecl und Microsoft stdcall. Mit diesen Konventionen legst du ja auch fest, wer den Stack aufräumt.
Rückgabewerte gehen fast immer über EAX, bei jedem Compiler.


In meinem Beispiel in ASM und in der Präsentation habe ich es nun im Gegensatz zu Delphi immer auf den Stack gelegt. Wäre also als wenn man die Funktion TTR mit "stdcall" deklariert. Das liegt nun an den Vor- und Nachteilen der einzelnen Aufrufkonventionen. Die Variante register ist eigentlich nur sinnvoll, wenn ich nachher gleich mit der Variablen im Register weiterarbeite. In unserer Funktion hätte ich sie aber trotzdem auf den Stack legen müssen. Es war in dem Fall also egal, ob die aufrufende Funktion den wert auf den Stack legt oder erst die aufgerufene Funktion. Denn zwischenspeichern muss sich die aufgerufeene Funktion den Wert irgendwo. Und sowas geht nur im Stack (bis auf wenige Ausnahmen).

Im Grunde genommen landet eigentlich alles, was du dir irgendwie merken musst auf dem Stack. Wenn dzu zum Beispiel eine Klasse instanzierst liegt die zwar irgendwo im Speicher mit ihren Eigenschaften, aber der Zeiger, der auf die Instanz zeigt, liegt bei dir auf dem Stack. so ist das mit allen anderen Sachen auch. Irgendwo musst du dir ja merken, wo was liegt. Einen anderen Speicher hast du ja nicht.


Edit: Hab gad gemerkt, dass ich dir auf deine direkte Frage wahrscheinlich noch nicht wirklich geantwortet habe:
Zitat:
Wird der Speicherplatz für das Funktionsergebnis tatsächlich vor dem Funktionsaufruf auf dem Stack reserviert?
Naja, du hast ja lokale Variablen. Auch in einer Funktion, in der du keine deklarierst legt dir Delphi die Variable "result" bereit. Und lokale Variablen legt man standardmäßig (auch Delphi macht das) auf den Stack. Wenn du selber ASM programmierst, kannst du dir evtl. überlegen, trotz einer lokalen Variable, keinen Stackplatz zu reservieren. Ein Compiler hat allerdings einen Algorithmus und der ist nicht menschlich. Er macht zwar (hoffentlich) keine Fehler kann aber auch nicht perfekt optimieren. Delphi macht das zwar und legt besonders Schleifenvariablen nicht auf den Stack, aber alles kann der Compiler auch nicht. Vielleicht schafft er es auch manchmal mit Result. Aber was bedeutet es, wenn du eine lokale Variable nicht auf den Stack legst. Du brauchst ein freies Register. Entweder du hast es nicht, oder du musst es dir erst frei machen, was bedeutet du musst dir den Inhalt auf den Stack legen --> wieder keinen Stackplatz gespart. Ok, soweit dazu, noch ein kurzes Beipsiel, wie sowas funktioniert:
Delphi-Quellcode:
//Beispiel zur Verwendung von lokalen Variablen
function test:integer;
{und im Beispiel für 4 Variablen (à 4 Bytes)
var a,b,c,d:integer;}

asm
  push ebp //jedes Register was du manipulierst musst du vorher speichern (ausser eax,edx und ecx)
  mov ebp,esp //aktuellen stackpointer als Referenz in Basisregister
  sub esp,20 //jetzt Platz schaffen für 4 Variablen+result =20 Bytes
               //in wirklichkeit legt man nicht oben auf den Stack drauf, sondern schiebt unten rein,
               //deswegen Subtraktion
               //Variable a liegt jetzt bei [ebp-4], b bei [ebp-8] ... ; So heißen die Variablen
               //dann nach dem Compiler
               //result liegt bei [ebp-20]
//hier kann man jetzt rechnen und was sonst noch und wenn ein weiterer Funktionsuafruf hier kommt, ist der Stackpointer bereits hinter unseren lokalen Variablen, sodass die nicht verändert werden und die aufrufende Funktion ist verpflichtet unser ebp zu retten, so wie wir es auch für die uns übergeordnete Funktion getan haben
//Beispiel für Funktionsaufruf
  push [ebp-4] //Variable a wird übergeben (auf den Stack gelegt)
                //in Delphi würde sie eher über eax übergeben, also: mov eax,[ebp-4]
  call meineFunktion //Funktionsaufruf (implizit wird Rücksprungadresse gesetzt)
  mov [ebp-8],eax //Funktionsergebnis (aus EAX) wird in b gespeichert
//in delphi sehe das so aus: b:=meineFunktion(a);


//Am Ende müssen wir natürlich wieder alles herrichten
  mov eax,[ebp-20] //result nach eax, wie gesagt, wenn man eax sonst nicht benötigt,
                    //was sehr unwahrscheinlich ist, könnte man result gleich in eax vorhalten
  mov esp,ebp
  pop ebp //das dürfte jetzt klar sein
end;
Noch ein kleine Korrektur aus deinem Text: Es wird erst der Stackpointer erhöht (oder wie eben gesehen erniedrigt) und dann der Wert reingeschrieben, so dass der Stackpointer nicht auf einen leeren Platz, sondern auf den letzten benutzten Platz zeigt.


mfg
Sirius
Dieser Beitrag ist für Jugendliche unter 18 Jahren nicht geeignet.
  Mit Zitat antworten Zitat