Anmerkung: Diese Antwort lautet Weg zu lang. Irgendwann werde ich es kürzen. In der Zwischenzeit können Sie Kommentare abgeben, wenn Sie nützliche Änderungen vorschlagen können.
Um Ihre Fragen zu beantworten, müssen wir zunächst zwei Speicherbereiche definieren, die Stapel und die Haufen .
Der Stapel
Stellen Sie sich den Stapel als einen Stapel von Kisten vor. Jedes Kästchen steht für die Ausführung einer Funktion. Zu Beginn, wenn main
angerufen wird, steht eine Kiste auf dem Boden. Alle lokalen Variablen, die Sie definieren, befinden sich in dieser Box.
Ein einfaches Beispiel
int main(int argc, char * argv[])
{
int a = 3;
int b = 4;
return a + b;
}
In diesem Fall haben Sie eine Box auf dem Boden mit den Variablen argc
(eine ganze Zahl), argv
(ein Zeiger auf ein Char-Array), a
(eine ganze Zahl), und b
(eine ganze Zahl).
Mehr als eine Box
int main(int argc, char * argv[])
{
int a = 3;
int b = 4;
return do_stuff(a, b);
}
int do_stuff(int a, int b)
{
int c = a + b;
c++;
return c;
}
Jetzt haben Sie eine Kiste auf dem Boden (für main
) mit argc
, argv
, a
und b
. Über diesem Feld befindet sich ein weiteres Feld (für do_stuff
) mit a
, b
und c
.
Dieses Beispiel veranschaulicht zwei interessante Effekte.
-
Wie Sie wahrscheinlich wissen, a
y b
wurden als Wert übergeben. Deshalb gibt es eine kopieren. dieser Variablen in das Feld für do_stuff
.
-
Beachten Sie, dass Sie nicht verpflichtet sind free
o delete
oder etwas anderes für diese Variablen. Wenn Ihre Funktion zurückkehrt, wird das Feld für diese Funktion zerstört.
Überlauf der Box
int main(int argc, char * argv[])
{
int a = 3;
int b = 4;
return do_stuff(a, b);
}
int do_stuff(int a, int b)
{
return do_stuff(a, b);
}
Hier haben Sie eine Box auf dem Boden (für main
(wie zuvor). Dann haben Sie ein Feld (für do_stuff
) mit a
y b
. Dann haben Sie ein weiteres Feld (für do_stuff
selbst aufrufen), wiederum mit a
y b
. Und dann noch einen. Und bald haben Sie einen Stapel Überlauf .
Zusammenfassung des Stapels
Stellen Sie sich den Stapel als einen Stapel von Kisten vor. Jedes Kästchen steht für eine ausgeführte Funktion, und dieses Kästchen enthält die in dieser Funktion definierten lokalen Variablen. Wenn die Funktion zurückkehrt, wird das Kästchen zerstört.
Mehr technisches Material
- Jede "Box" wird offiziell als Stapelrahmen .
- Ist Ihnen schon einmal aufgefallen, dass Ihre Variablen "zufällige" Standardwerte haben? Wenn ein alter Stack-Frame "zerstört" wird, ist er einfach nicht mehr relevant. Er wird nicht auf Null gesetzt oder dergleichen. Wenn das nächste Mal ein Stackframe diesen Speicherbereich verwendet, sehen Sie Bits des alten Stackframes in Ihren lokalen Variablen.
Der Haufen
Hier kommt die dynamische Speicherzuweisung ins Spiel.
Stellen Sie sich den Haufen als eine endlose grüne Wiese der Erinnerung vor. Wenn du anrufst malloc
o new
wird ein Speicherblock auf dem Heap zugewiesen. Sie erhalten einen Zeiger, um auf diesen Speicherblock zuzugreifen.
int main(int argc, char * argv[])
{
int * a = new int;
return *a;
}
Hier wird eine neue ganze Zahl an Speicher auf dem Heap zugewiesen. Sie erhalten einen Zeiger namens a
die auf diese Erinnerung verweist.
a
ist eine lokale Variable und befindet sich daher in main
Die "Box"
Gründe für die dynamische Speicherzuweisung
Sicher, die Verwendung von dynamisch zugewiesenem Speicher scheint hier und da ein paar Bytes für Zeiger zu verschwenden. Allerdings gibt es Dinge, die man ohne dynamische Speicherzuweisung einfach nicht (leicht) erledigen kann.
Rückgabe eines Arrays
int main(int argc, char * argv[])
{
int * intarray = create_array();
return intarray[0];
}
int * create_array()
{
int intarray[5];
intarray[0] = 0;
return intarray;
}
Was passiert hier? Sie "geben ein Array zurück" in create_array
. In Wirklichkeit geben Sie einen Zeiger zurück, der nur auf den Teil der create_array
"Box", die das Array enthält. Was passiert, wenn create_array
zurück? Seine Box ist zerstört, und Sie können davon ausgehen, dass Ihr Array jeden Moment beschädigt werden kann.
Verwenden Sie stattdessen dynamisch zugewiesenen Speicher.
int main(int argc, char * argv[])
{
int * intarray = create_array();
int return_value = intarray[0];
delete[] intarray;
return return_value;
}
int * create_array()
{
int * intarray = new int[5];
intarray[0] = 0;
return intarray;
}
Da die Rückgabe einer Funktion den Heap nicht verändert, wird Ihr wertvolles intarray
entkommt unversehrt. Denken Sie daran delete[]
wenn Sie fertig sind.