Menu

Dynamischer Speicher in C++: new und delete erklärt

Wie man zur Laufzeit mit new Speicher reserviert, ihn mit delete freigibt und die Lecks, hängenden Zeiger und doppelten Freigaben vermeidet, die mit der manuellen Verwaltung des Heaps einhergehen.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Warum du zur Laufzeit Speicher anforderst

Bisher lebte jede von dir erstellte Variable auf dem Stack: Ihre Größe ist zur Kompilierzeit bekannt und sie wird automatisch zerstört, wenn ihr Gültigkeitsbereich endet. Das ist schnell und sicher, kann aber den Fall nicht abdecken, in dem du erst zur Laufzeit weißt, wie viel Speicher du brauchst - ein Puffer, dessen Größe von der Benutzereingabe abhängt, eine Struktur, die die Funktion überleben muss, die sie erzeugt hat, oder ein Graph, dessen Form vorab nicht bekannt ist.

Für diese Fälle erlaubt dir C++, mit new vom Heap (auch Free Store genannt) zu reservieren und ihn mit delete zurückzugeben. Der new-Ausdruck reserviert einen Block, ruft den Konstruktor auf und gibt einen Zeiger darauf zurück - er baut direkt auf den Zeigern auf, die du auf der vorigen Seite gesehen hast.

Die Variable p selbst lebt auf dem Stack - sie ist nur ein Zeiger. Das int, auf das sie zeigt, lebt auf dem Heap und bleibt am Leben, bis du es mit delete freigibst, egal wie viele Gültigkeitsbereiche kommen und gehen.

Stack vs. Heap

Diese Unterscheidung ist der eigentliche Grund, warum es new gibt, daher lohnt es sich, sie konkret zu machen.

void demo() {
    int a = 10;            // auf dem Stack - weg, sobald demo() zurückkehrt
    int* b = new int(10);  // 'b' auf dem Stack, das int, auf das es zeigt, auf dem Heap
}                          // 'a' zerstört; das Heap-int LECKT - nie freigegeben

Wichtige Unterschiede:

  • Stack - automatische Lebensdauer, sehr schnell, begrenzte Größe (typischerweise einige MB), wird beim Verlassen des Gültigkeitsbereichs für dich freigegeben.
  • Heap - manuelle Lebensdauer, etwas langsamer, groß und wird nur freigegeben, wenn du delete aufrufst.

Der Handel ist Flexibilität gegen Verantwortung: Heap-Speicher lebt genau so lange, wie du willst, aber du wirst zu demjenigen, der daran denken muss, ihn freizugeben.

Arrays mit new[] reservieren

Wenn du einen Block brauchst, dessen Länge zur Laufzeit festgelegt wird, verwende die Array-Form new T[n]. Sie gibt einen Zeiger auf das erste Element zurück, und du gibst ihn mit dem passenden delete[] frei.

Die Regel ist streng und leicht falsch zu machen: Speicher von new wird mit delete freigegeben, und Speicher von new[] wird mit delete[] freigegeben. Sie zu vermischen - delete arr auf etwas, das mit new[] reserviert wurde - ist undefiniertes Verhalten, selbst wenn es auf deinem Rechner zu funktionieren scheint.

Die drei klassischen Bugs

Die manuelle Speicherverwaltung hat eine kleine Menge an Fehlern, die für die meisten Heap-Bugs verantwortlich sind. Lerne, alle drei zu erkennen.

1. Speicherleck - du rufst delete nie auf. Der Block bleibt für immer reserviert. Einmal harmlos, in einer Schleife fatal.

void leaky() {
    int* p = new int(5);
    // ... kein delete ...
}   // p ist weg; das Heap-int ist jetzt unerreichbar UND nicht freigegeben

2. Hängender Zeiger - du benutzt Speicher, nachdem du ihn freigegeben hast. Der Zeiger enthält noch die alte Adresse, aber dieser Speicher gehört dir nicht mehr.

3. Doppelte Freigabe - du machst delete zweimal auf denselben Block. Das beschädigt die interne Buchführung des Heaps und führt meist zu einem Absturz.

int* p = new int(1);
delete p;
delete p;   // doppelte Freigabe - undefiniertes Verhalten, oft ein Absturz

Einen Zeiger nach dem Freigeben auf nullptr zu setzen entschärft sowohl den hängenden Gebrauch als auch die doppelte Freigabe: das Dereferenzieren von nullptr stürzt sofort ab (leicht zu debuggen), und delete nullptr ist ausdrücklich eine sichere Operation, die nichts tut.

Ein realistischer Reservieren-Benutzen-Freigeben-Zyklus

Alles zusammengenommen sieht korrekte manuelle Verwaltung so aus: reservieren, benutzen, genau einmal freigeben und den Zeiger danach nicht mehr anfassen.

Beachte, dass delete u bei einem Klassentyp zwei Dinge tut: Es führt zuerst den Destruktor des Objekts aus und gibt dann den rohen Speicher frei. Diese Reihenfolge ist wichtig, sobald deine Objekte eigene Ressourcen besitzen.

Ein subtiler Fallstrick: Wird zwischen new und delete eine Ausnahme geworfen, läuft das delete nie und du leckst Speicher. Jede Reservierung in try/catch zu hüllen, um das abzufangen, ist mühsam und fehleranfällig - genau das ist das Problem, das die nächste Seite löst.

Als Nächstes: Smart Pointer

Du hast jetzt die vollen Kosten der manuellen Speicherverwaltung gesehen: Jedes new ist ein Versprechen, später delete aufzurufen, und eine einzige vergessene, doppelte oder zu frühe Freigabe ist undefiniertes Verhalten. Modernes C++ gibt dieses Versprechen fast nie von Hand. Die nächste Seite stellt Smart Pointer vor - std::unique_ptr und std::shared_ptr - Objekte, die eine Heap-Reservierung besitzen und automatisch delete für dich aufrufen, wenn sie ihren Gültigkeitsbereich verlassen, wodurch alle drei klassischen Bugs zu Dingen werden, um die sich der Compiler und RAII an deiner Stelle kümmern.

Häufig gestellte Fragen

Was ist der Unterschied zwischen new und delete in C++?

new reserviert zur Laufzeit Speicher auf dem Heap und gibt einen Zeiger darauf zurück; delete gibt Speicher frei, der mit new reserviert wurde. Jedes new muss mit genau einem delete gepaart werden, sonst entsteht ein Speicherleck. Für Arrays verwendest du new[] zusammen mit delete[].

Was passiert, wenn man in C++ vergisst, delete aufzurufen?

Es entsteht ein Speicherleck: Der Heap-Block bleibt für die gesamte Laufzeit deines Programms reserviert, obwohl nichts mehr darauf zeigt. Ein einzelnes Leck ist meist harmlos, aber Lecks in einer Schleife oder in einem langlaufenden Dienst wachsen, bis dem Programm der Speicher ausgeht und es abstürzt.

Sollte ich new und delete in modernem C++ direkt verwenden?

Selten. Bevorzuge Container wie std::vector oder Smart Pointer (std::unique_ptr, std::shared_ptr), die den Speicher automatisch freigeben. Rohes new/delete zu verstehen lohnt sich, weil Smart Pointer es kapseln, aber im Alltagscode ist es eine Quelle für Lecks und hängende Zeiger.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S