11 Stimmen

Unterschiede zwischen dynamischem Speicher und "normalem" Speicher

Was sind einige der technischen Unterschiede zwischen Speicher, der mit dem new Operator und Speicher, der über eine einfache Variablendeklaration zugewiesen wird, wie z. B. int var ? Verfügt C++ über irgendeine Form der automatischen Speicherverwaltung?

Ich habe insbesondere ein paar Fragen. Erstens, da man bei dynamischem Speicher einen Zeiger deklarieren muss, um die Adresse des tatsächlichen Speichers zu speichern, mit dem man arbeitet, verwendet der dynamische Speicher nicht mehr Gedächtnis? Ich sehe nicht, warum der Zeiger überhaupt notwendig ist, es sei denn, Sie sind ein Array deklarieren.

Zweitens, wenn ich eine einfache Funktion wie die folgende erstellen würde:

int myfunc() { int x = 2; int y = 3; return x+y; }

...und es aufrufen, würde der von der Funktion zugewiesene Speicher freigegeben werden, sobald sein Existenzbereich beendet ist? Was ist mit dem dynamischen Speicher?

20voto

Wesley Punkte 10357

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.

  1. 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 .

  2. 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.

4voto

Raul Agrait Punkte 5820

Der dynamische Speicher befindet sich auf dem Heap und nicht auf dem Stack. Die Lebensdauer des dynamischen Speichers erstreckt sich vom Zeitpunkt der Zuweisung bis zum Zeitpunkt der Freigabe. Bei lokalen Variablen ist ihre Lebensdauer auf die Funktion/den Block beschränkt, in der/dem sie definiert sind.

Was Ihre Frage nach dem Speicherverbrauch in der Funktion betrifft, so würde in Ihrem Beispiel der Speicher für Ihre lokalen Variablen am Ende der Funktion freigegeben werden. Wenn der Speicher jedoch dynamisch zugewiesen wurde mit new wird er nicht automatisch entsorgt, sondern Sie müssen ihn explizit mit delete um den Speicher freizugeben.

Was die automatische Speicherverwaltung betrifft, so bietet die C++-Standardbibliothek auto_ptr dafür.

4voto

Fragsworth Punkte 31001

Durch "new" zugewiesener Speicher landet auf dem Heap.

Der in einer Funktion zugewiesene Speicher befindet sich innerhalb der Funktion, wo die Funktion auf dem Stapel liegt.

Lesen Sie hier über die Zuordnung von Stack und Heap: http://www-ee.eng.hawaii.edu/~tep/EE160/Buch/chap14/unterabschnitt2.1.1.8.html

2voto

Kosi2801 Punkte 20758

Der mit dem new-Operator zugewiesene Speicher wird aus einem "Heap" genannten Speicherbereich geholt, während statische Zuweisungen für Variablen einen mit Prozedur-/Funktionsaufrufen geteilten Speicherbereich (den "Stack") verwenden.

Sie müssen sich nur um die dynamischen Speicherzuweisungen kümmern, die Sie selbst mit new vorgenommen haben. Variablen, die zur Kompilierzeit bekannt sind (im Quelltext definiert), werden am Ende ihres Anwendungsbereichs (Ende der Funktion/Prozedur, des Blocks, ...) automatisch freigegeben.

1voto

Juergen Punkte 11770

Der große Unterschied zwischen "dynamischem" und "normalem" Speicher wurde in der Frage selbst recht gut wiedergegeben.

Dynamischer Speicher wird von C++ nicht allzu gut unterstützt.

Wenn Sie dynamischen Speicher verwenden, sind Sie selbst dafür verantwortlich. Sie müssen ihn zuweisen. Wenn Sie das vergessen und versuchen, über Ihren Zeiger auf ihn zuzugreifen, werden Sie viele negative Überraschungen erleben. Außerdem müssen Sie den Speicher wieder freigeben - und wenn Sie das auf irgendeine Weise vergessen, werden Sie noch mehr Überraschungen erleben. Solche Fehler gehören zu den am schwierigsten zu findenden Fehlern in C/C++-Programmen.

Sie benötigen einen zusätzlichen Zeiger, da Sie irgendwie auf Ihren neuen Speicher zugreifen müssen. Ein gewisser Speicher (ob dynamisch oder nicht) ist zunächst einmal nichts, womit eine Programmiersprache umgehen kann. Sie müssen Zugriff darauf haben. Dies geschieht durch Variablen. Aber Variablen in Sprachen wie C++ werden im "normalen" Speicher gespeichert. Also braucht man "Zeiger" - Zeiger sind eine Form der Umleitung, die sagt: "Nein, ich bin nicht der Wert, nach dem du suchst, aber ich zeige auf ihn". Zeiger sind die einzige Möglichkeit in C++, auf dynamischen Speicher zuzugreifen.

Im Gegensatz dazu kann auf "normalen" Speicher direkt zugegriffen werden, die Zuweisung und Freigabe erfolgt automatisch durch die Sprache selbst.

Dynamischer Speicher und Zeiger sind die größte Quelle für Probleme in C++ - aber es ist auch ein sehr mächtiges Konzept - wenn man es richtig macht, kann man viel mehr tun als mit gewöhnlichem Speicher.

Das ist auch der Grund, warum viele Bibliotheken Funktionen oder ganze Module für den Umgang mit dynamischem Speicher haben. Das auto_ptr-Beispiel wurde auch in einer parallelen Antwort erwähnt, die versucht, mit dem Problem umzugehen, dass dynamischer Speicher am Ende einer Methode zuverlässig freigegeben werden sollte.

Normalerweise werden Sie den dynamischen Speicher nur dann verwenden, wenn Sie ihn wirklich brauchen. Sie werden ihn nicht verwenden, um eine einzelne Integer-Variable zu haben, sondern um Arrays zu haben oder größere Datenstrukturen im Speicher aufzubauen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X