C++-Programmierung/ Speicherverwaltung/ new und delete

Aus Wikibooks
Zur Navigation springen Zur Suche springen


C++ bietet die Möglichkeit der sogenannten Freispeicherverwaltung. Das heißt, Sie können zu jedem Zeitpunkt eine beliebige Menge an Speicher anfordern (vom Betriebssystem). Allerdings müssen Sie diesen Speicher später auch selbst wieder frei geben (ans Betriebssystem zurückgeben), was Gefahren nach sich zieht: Sie können vergessen, nicht mehr benötigten Speicher zurückzugeben - das Betriebssystem kann ohne einen expliziten Hinweis (also das Freigeben des Speichers durch Ihr Programm) nicht wissen, ob der Speicher noch benötigt wird. Das unnötige Vorhandensein nicht mehr benötigten Speichers bezeichnet man als Speicherleck (engl. „Memory Leak“) oder auch Speicherleiche. Umgekehrt wird auch das versehentliche Freigeben von eigentlich noch benötigtem Speicher als Speicherleck bezeichnet (es führt zu "dangling pointers").

Andere Sprachen, wie etwa Java, rücken diesem Problem mit einer sogenannten „Garbage Collection“ (englisch für Müllabfuhr; in diesem Fall für „Automatische Speicherbereinigung“) zu Leibe. Dabei wird im Hintergrund periodisch nach vom Programm angeforderten Speicherbereichen gesucht, auf die keine Variable/Zeiger im Programm mehr verweist. C++ besitzt kein derartiges Werkzeug, was zwar zu einer höheren Performance führt, aber andererseits auch eine besondere Sorgfalt von Ihnen als Programmierer verlangt. Im Lauf dieses Kapitels werden Sie jedoch Hilfsmittel kennenlernen, die Sie tatkräftig bei der Vermeidung von Speicherlecks unterstützen.

Anforderung und Freigabe von Speicher[Bearbeiten]

Angefordert wird Speicher in C++ über den Operator new. Im Gegensatz zur C-Variante wird in C++ immer Speicher für einen bestimmten Datentyp angefordert. Dieser darf allerdings nicht const oder volatile und auch keine Funktion sein. Der new-Operator erhält als Argument also einen Datentyp, anhand dessen er die Menge des benötigten Speichers selbstständig bestimmt. An den Konstruktor des Datentyps können, wenn nötig, Argumente übergeben werden, denn new gibt nicht nur einen Speicherbereich zurück, sondern legt in diesem Speicherbereich auch gleich noch ein Objekt des übergebenen Typs an. Zu beachten ist hierbei, dass für einen Aufruf ohne zusätzliche Konstruktorargumente keine leeren Klammern angegeben werden dürfen.

Crystal Project Tutorials.png
Syntax:
new «Datentyp» »(«Konstruktorargumente»)«
«Nicht-C++-Code», »optional«

new gibt bei erfolgreicher Durchführung einen Zeiger auf den entsprechenden Datentyp zurück. Diesen Zeiger müssen Sie dann (beispielsweise in einer Variable) speichern. Wenn Sie das Objekt nicht mehr benötigen, müssen Sie es mittels delete-Operator wieder freigeben. delete erwartet zum Löschen den Zeiger, den new Ihnen geliefert hat. Analog zum Konstruktoraufruf bei new ruft delete zunächst den Destruktor des Objekts auf und gibt anschließend den Speicher an das Betriebssystem zurück.

Crystal Project Tutorials.png
Syntax:
delete «Speicheradresse»
«Nicht-C++-Code», »optional«
Symbol opinion vote.svg
Hinweis

Lassen Sie niemals eine Speicheradresse zweimal durch delete löschen, denn dies führt fast immer zum Programmabsturz. Am einfachsten vermeiden Sie dies, indem Sie Ihre Zeigervariable nach dem Löschen auf 0 setzen, denn das Löschen eines Nullzeigers durch delete ist gestattet.

Die Situation, dass ein Speicherbereich zweimalig freigegeben werden soll, ist zudem meist ein Hinweis auf eine inkonsistente Programmstruktur - etwas stimmt mit der Programmlogik nicht.

Im Folgenden sehen Sie noch mal ein kleines Beispiel, bei dem ein double dynamisch angelegt wird. Dabei wird beim zweiten Anlegen ein Argument zur Initialisierung angegeben, was einem Konstruktorargument entspricht.

Nuvola-inspired-terminal.svg
 1 #include <iostream>
 2 
 3 int main(){
 4     double* zeiger = new double;       // Anfordern eines doubles
 5 
 6     std::cout << *zeiger << std::endl; // gibt den (zufälligen) Wert des doubles aus
 7 
 8     delete zeiger;                     // Löschen der Variable
 9     zeiger = 0;                        // Zeiger auf 0 setzen
10     delete zeiger;                     // ok, weil Zeiger 0 ist (nur zur Vorführung)
11 
12     zeiger = new double(7.5);          // Anfordern eines doubles, initialisieren mit 7.5
13 
14     std::cout << *zeiger << std::endl; // gibt 7.5 aus
15 
16     delete zeiger;                     // Löschen der Variable
17 }

Falls beim Anfordern des Speichers durch new etwas schief geht, etwa weil nicht mehr genug Speicher zur Verfügung steht, wird eine std::bad_alloc-Ausnahme geworfen. Sie können also nach einem new-Aufruf davon ausgehen, dass dieser erfolgreich war, müssen aber mittels eines try-Blocks eventuell geworfene Ausnahmen abfangen. Mehr Informationen hierzu finden Sie im Kapitel „Übersicht“ zur Ausnahmebehandlung.