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 x | Start set7 | y=7 | Ende set7 | Start printf | Ende printf | Ende |
|
|
|
|
|
|
|
|
| | | | | 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
|