7.6 Exkurs: Funktionen bei der Ausführung
Diesen Abschnitt können Sie überspringen, wenn Ihnen die folgenden Erklärungen zur Prozessorarchitektur und zum Stack zu schwierig erscheinen. Wir haben diesen Abschnitt hier nur der Vollständigkeit halber eingefügt, und Sie können auch ohne diesen C erlernen.
Wenn Sie eine Funktion aufrufen, werden einige Mechanismen in Gang gesetzt, die einen reibungslosen Ablauf gewährleisten. Gerade bei der Verwendung von lokalen Variablen (siehe Abschnitt 7.11, »Globale, lokale und statische Variablen«) und der Rückgabe von Werten aus Funktionen ist es hilfreich (aber nicht notwendig) zu wissen, was hinter den Kulissen beim Funktionsaufruf geschieht.
Rufen Sie eine Funktion auf, werden einige Daten auf einer Art Stapel (auch Stack genannt) abgelegt, z. B. der aktuelle Zustand der Prozessorregister oder auch die Rücksprungadresse, damit die Funktion nach der Beendigung wieder in der korrekten Weise zum Hauptprogramm zurückkehren kann. Auch die Funktionsparameter werden über den Stack übergeben. Der Prozessor verwaltet also in der Tat eine Art Kartenstapel, auf dem er die Daten ablegen kann und von dem er auch wieder Daten herunterziehen kann. Konkret heißt dies, dass bei einem Funktionsaufruf auf dem Stack der entsprechende Speicherplatz für die Rücksprungadresse und die übergebenen Parameter reserviert wird. Dies leistet der Prozessor, indem er spezielle Befehle benutzt, um den Stack zu verwalten. Meistens heißen diese Befehle PUSH (Daten auf den Stack schieben) und POP bzw. PULL (Daten vom Stack herunterziehen). Der Wert, der als letzter abgelegt wurde, wird natürlich auch wieder als Erstes von Stapel gezogen. Dies funktioniert in der Tat wie bei einem Kartenstapel. Wenn Sie als Letztes einen König abgelegt haben, werden Sie keine Dame vom Stapel herunterziehen.
Bei modernen Prozessoren hat nun jedes Programm einen eigenen Speicherbereich für den Stack, den man auch Stack-Rahmen nennt. Auf diesem Stackrahmen wird bei Bedarf Speicher reserviert und wieder freigegeben. Auf dem Stack wird also für jeden Funktionsaufruf ein Datenblock abgelegt. In diesem Datenblock werden die formalen Parameter, die lokalen Variablen und die Rücksprungadresse zum Aufrufer gespeichert. Auch die Prozessorregister, die gewisse Zustände anzeigen (z. B. das Flag-Register) werden auf dem Stack gesichert.
Wird eine Funktion mit return beendet, oder erreicht die Funktion das Ende des Bezugsrahmens (also des Anweisungsblocks), werden diese Daten im Stack-Rahmen wieder freigegeben (also wieder vom Stapel gezogen). Das bedeutet natürlich, dass alle lokalen Variablen, die in einer Funktion definiert wurden, nach dem Ende der Funktion verloren sind.
Wenn Sie innerhalb einer Funktion eine weitere Funktion aufrufen, wird ein zusätzlicher Datenblock auf dem Stack unter dem Datenblock der aktuellen Funktion angelegt.