Die Computerseite von Eckart Winkler
Die Rolle des Stacks in C - Teil 1

 

Ihnen ist ja bekannt, daß für jede Variable, die Sie in Ihrem C-Programm benutzen, eine passende Speicherstelle reserviert wird, für die der Name der Variablen stellvertretend steht. Jeder Zugriff auf die Variable ist in Wirklichkeit ein Zugriff auf diese Speicherstelle.

Sie wissen weiterhin, daß Sie die Werte von Variablen an Funktionen übergeben können. Sehen Sie sich einmal das folgende Beispiel an:

#include <stdio.h>
void set7(short x);

void main()
{
  short x=5;
  set7(x);
  printf("x=%d\n",x);
}

void set7(short y)
{
  y=7;
}

In der Funktion main wird die Variable x definiert und mit dem Wert 5 initialisiert. Zunächst sieht es so aus, als würde x durch Aufruf der Funktion set7 den Wert 7 erhalten. Sicher ist Ihnen aber bekannt, daß das nicht funktioniert. Denn da der Parameter der Funktion in dieser eine lokale Variable, hier y genannt, ist, kann zwar der Wert von y verändert werden, nie aber der Wert der übergebenen Variablen x. In C findet eben generell eine Übergabe des Werts statt, nicht einer Referenz ("call by value" im Gegensatz zu "call by reference").

Probieren Sie das bitte aus, auf dem Bildschirm wird in jedem Fall die Ausgabe x=5 erscheinen.

Es ist zum einen wichtig, sich dieser Tatsache bewußt zu sein. Denn dies muß in Ihren C-Programmen natürlich berücksichtigt werden. Zum anderen ist es aber auch sehr nützlich zu wissen, warum das so ist. Denn es wird immer Fälle geben, in denen Ihnen die Kenntnis der Abläufe die auftretenden Fragen sofort beantwortet, und dies kann ja nicht von Nachteil sein.


Der Stack

Grundlage der Übergabe von Parametern an Funktionen ist der Stapelspeicher, meist mit dem englischen Wort Stack bezeichnet. Stellen Sie sich hierbei einfach einen Stapel Papier vor. Die aufrufende Funktion legt für jeden an die Funktion zu übergebenden Parameter ein Blatt Papier auf den Stapel, auf dem der Wert der Variablen geschrieben steht. Die aufgerufene Funktion kann sich die obenliegenden Blätter ansehen und sogar für eigene Berechnungen benutzen. Ist die aufgerufene Funktion beendet, werden die Blätter wieder vom Stapel entfernt und weggeworfen.

Was heißt das jetzt für unser obiges Beispiel? Nun, die Funktion set7 hat genau einen Parameter. Dementsprechend muß, um bei dem Bild zu bleiben, beim Aufruf der Funktion der Wert der Variablen x, also 5, auf ein Blatt Papier geschrieben werden. Dieses Blatt wird auf den Papierstapel gelegt. In der Funktion kann der Wert des Parameters, von dem Blatt abgelesen werden. Ebenso kann das Blatt als lokale Variable benutzt werden. Die Zuweisung y=7 bedeutet also nichts anderes, als daß auf dem Blatt die 5 durchgestrichen und stattdessen 7 daraufgeschrieben wird. Nach Beendigung der Funktion wird das Blatt aber vom Stapel entfernt und weggeworfen.

Dadurch wird klar, daß die Zuweisung y=7 nur innerhalb der Funktion set7 eine Wirkung hat. Denn innerhalb der Funktion kann jederzeit wieder auf das Blatt Papier bzw. die Variable y zugegriffen werden. Nach Ablauf der Funktion aber wird das Blatt Papier weggeworfen bzw. die Variable y zerstört. Ihr Wert ist nicht mehr verfügbar.

So viel zum allgemeinen Ablauf. Ganz genau ist das Bild mit dem Papierstapel allerdings noch nicht. Denn es gibt ja verschiedene Datentypen mit verschiedenen Größen, die da an eine Funktion übergeben werden können. Streng genommen bräuchte man also Papierblätter verschiedener Größe, um Parameter von verschiedener Größe übergeben zu können. Oder aber kleinere Blätter, von denen gemäß der Größe jeweils mehrere benutzt werden müssen, um einen Wert an die Funktion zu übergeben.

Und genau das ist es. Die in der EDV allgegenwärtige Größeneinheit Byte ist es, die einem solchen Blatt entspricht. Soll also ein short-Wert an eine Funktion übergeben werden, müssen 2 Byte auf den Stack gelegt werden, denn ein short-Wert hat die Größe 2 Byte. Soll ein long-Wert übergeben werden, müssen 4 Byte auf den Stack gelegt werden.


Prototypen

Das wirft natürlich eine Frage auf: Könnte etwa beim Aufruf einer Funktion ein long-Wert übergeben werden, obwohl die aufgerufene Funktion zwei short-Werte erwartet? Die Antwort kennen Sie: Nein, denn mit Hilfe der Funktions-Prototypen kann der Compiler überprüfen, ob die Definition der Funktion mit den Aufrufen übereinstimmt.

Das ist allerdings nicht bei allen Funktionen möglich. Denken Sie an die Funktion printf, bei der sowohl Anzahl als auch Art der Parameter variabel sind. Sie wissen aber auch, wie leicht Sie ein Programm zum Absturz bringen können, wenn Sie printf mit falschen Parametern aufrufen.

Nun wird der Stack nicht nur zur Parameterübergabe zwischen Funktionen verwendet. Nein, auch die lokalen Variablen der Funktionen erhalten dort ihren Platz. Das ist durchaus sinnvoll. Denn nach Beendigung der Funktion können mit einem Schlag sowohl die lokalen Variablen als auch die Parameter vom Stack entfernt werden.

Jetzt sollten Sie noch einmal kurz überlegen, was auf dem Stack bei diesem kurzen Beispielprogramm vor sich geht: Zu Beginn des Programms ist der Stack natürlich leer. Dann wird die Funktion main ausgeführt. Diese hat eine lokale Variable, nämlich x. Für diese werden 2 Bytes auf dem Stack reserviert. Dann der Aufruf von set7. Diese Funktion hat einen Parameter vom Typ short. Dementsprechend werden weitere 2 Bytes auf den Stack gelegt, die zwar denselben Wert repräsentieren wie die ersten, von diesen aber verschieden sind.

Die Wertzuweisung y=7 ändert den oben auf dem Stack liegenden Wert, dieser wird aber nach Beendigung der Funktion wieder vom Stack entfernt. Dann kommt ein weiterer Funktionsaufruf, und zwar printf. Auch hierfür werden die Parameter auf den Stack gelegt. Diesmal gibt es zwei Parameter, und zwar den Formatstring "x=%d\n" und den Wert der Variablen x. Strings sind Arrays von Zeichen, die als Adresse an Funktionen übergeben werden.

Auf die nach wie vor auf dem Stack liegenden 2 Byte der lokalen Variablen x kommt nun die Adresse des Formatstrings (meist 4 Bytes) und darauf der Wert der Variablen x. Was intern in der Funktion printf passiert, ist unbekannt. Dies müssen wir in dieser Betrachtung als aussparen. Möglicherweise werden dort weitere lokale Variablen benutzt, die auf den Stack gelegt werden. Wie auch immer, nach Beendigung von printf sind auch diese wieder verschwunden. Es verbleibt der Wert der Variablen x. Dieser wird erst mit Beendigung des Programms vom Stack entfernt.


Tabellarische Darstellung

Und hier finden Sie dies alles noch einmal grafisch dargestellt. Jede Spalte der Tabelle entspricht dem Zustand des Stacks zum Zeitpunkt nach dem Ereigniss in der ersten Zeile:

Start short xStart set7y=7 Ende set7Start printf Ende printfEnde
                                                                                                                                       
        5    
   5 (=y) 7 (=y)  "x=%d\n"   
 5 (=x) 5 (=x) 5 (=x)5 (=x) 5 (=x) 5 (=x)  


Weiter mit Die Rolle des Stacks Teil 2

 

Übersicht Programmiersprache C Index