Mit dem Operator new kann man Variablen während der Laufzeit des Programms erzeugen. Dieser Operator versucht, in einem eigens dafür vorgesehenen Speicherbereich (der oft auch als Heap, dynamischer Speicher oder freier Speicher bezeichnet wird) so viele Bytes zu reservieren, wie eine Variable des angegebenen Datentyps benötigt.
Falls der angeforderte Speicher zur Verfügung gestellt werden konnte, liefert new seine Adresse zurück. Andernfalls wird eine Exception des Typs std::bad_alloc ausgelöst, die einen Programmabbruch zur Folge hat, wenn sie nicht mit einer try-
Anweisung abgefangen wird. Da unter 32-bit-Systemen wie Windows unabhängig vom physisch vorhandenen Hauptspeicher 2 GB virtueller Speicher zur Verfügung stehen, kann man meist davon ausgehen, dass der angeforderte Speicher verfügbar ist.
Variablen, die zur Laufzeit erzeugt werden, bezeichnet man auch als dynamisch erzeugte Variablen. Wenn der Unterschied zu vom Compiler erzeugten Variablen (wie „int i;“) betont werden soll, werden diese als „gewöhnliche“ Variablen bezeichnet. Die folgenden Beispiele zeigen, wie man new verwenden kann:
1. Für einen Datentyp T reserviert „new T“ so viele Bytes auf dem Heap, wie für eine Variable des Datentyps T notwendig sind. Der Ausdruck „new T“ hat den Datentyp „Zeiger auf T“. Sein Wert ist die Adresse des reservierten Speicherbereichs und kann einer Variablen des Typs „Zeiger auf T“ zugewiesen werden:
int* pi; pi=new int; //reserviert sizeof(int) Bytes und weist pi die Adresse dieses Speicherbereichs zu *pi=17; // initialisiert den reservierten Speicherbereich
Am besten initialisiert man eine Zeigervariable immer gleich bei ihrer Definition:
int* pi=new int; // initialisiert pi mit der Adresse *pi=17;
Die explizit geklammerte Version von new kann zur Vermeidung von Mehrdeutigkeiten verwendet werden. Für einfache Datentypen sind beide Versionen gleichwertig:
pi=new(int); // gleichwertig zu pi=new int;
2. In einem new-expression kann man nach dem Datentyp einen new-initializer angeben: Er bewirkt die Initialisierung der mit new erzeugten Variablen. Die zulässigen Ausdrücke und ihre Bedeutung hängen vom Datentyp der dynamisch erzeugten Variablen ab.
Für einen fundamentalen Datentyp (wie int, double usw.) gibt es drei Formen:
a) Der in Klammern angegebene Wert wird zur Initialisierung verwendet:
double* pd=new double(1.5); //Initialisierung *pd=1.5
b) Gibt man keinen Wert zwischen den Klammen an, wird die Variable mit 0 (NULL) initialisiert:
double* pd=new double(); //Initialisierung *pd=0
c) Ohne einen Initialisierer ist ihr Wert unbestimmt:
double* pd=new double;// *pd wird nicht initialisiert
Für einen Klassentyp müssen die Ausdrücke Argumente für einen Konstruktor sein.
3. Gibt man nach einem Datentyp T in eckigen Klammern einen ganzzahligen Ausdruck >= 0 an, wird ein Array dynamisch erzeugt reserviert.
Die Anzahl der Arrayelemente ist durch den ganzzahligen Ausdruck gegeben. Im Unterschied zu einem gewöhnlichen Array muss diese Zahl keine Konstante sein:
typedef double T; // T irgendein Datentyp, hier double int n=100; // nicht notwendig eine Konstante T* p=new T[n]; // reserviert n*sizeof(T) Bytes
Wenn durch einen new-Ausdruck ein Array dynamisch erzeugt wird, ist der Wert des Ausdrucks die Adresse des ersten Arrayelements. Die einzelnen Elemente des Arrays können folgendermaßen angesprochen werden:
p[0], ..., p[n–1] // n Elemente des Datentyps T
Die Elemente eines dynamisch erzeugten Arrays können nicht bei ihrer Definition initialisiert werden.
4. Mit dem nur selten eingesetzten new-placement kann man eine Variable an eine bestimmte Adresse platzieren. Damit wird keine Variable wie bei einem gewöhnlichen new-Ausdruck auf dem Heap angelegt. Die Adresse wird als Zeiger nach new angegeben und ist z.B. die Adresse eines zuvor reservierten Speicherbereichs:
int* pi=new int; double* pd=new(pi) double;// erfordert #include<new.h>
Durch die letzte Anweisung kann man den Speicherbereich ab der Adresse von i als double ansprechen wie nach
double* pd=(double*)pi; // explizite Typkonversion
Dieses Beispiel zeigt, dass die meisten Programme keine Anwendungen für ein new placement haben. Nützlichere Anwendungen gibt es bei Betriebssystemen, die (anders als Windows) Geräte über physikalische Adressen ansprechen.
5. Variable, deren Datentyp eine Klasse der VCL ist, müssen mit new angelegt werden. Dabei muss dem Konstruktor der Eigentümer (z.B. Form1) übergeben werden.
TEdit* pe = new TEdit(Form1); pe->Parent = Form1; pe ->SetBounds(10, 20, 100, 30); pe ->Text = "blablabla";
Es ist nicht möglich, VCL-Komponenten vom Compiler erzeugen zu lassen:
TEdit e; bzw. TEdit e(Form1); // Fehler
Eine gewöhnliche Variable existiert von ihrer Definition bis zum Ende des Bereichs (bei einer lokalen Variablen ist das der Block), in dem sie definiert wurde. Im Unterschied dazu existiert eine dynamisch erzeugte Variable bis der für sie reservierte Speicher mit dem Operator delete wieder freigegeben oder das Programm beendet wird. Man sagt auch, dass eine dynamisch erzeugte Variable durch den Aufruf von delete zerstört wird.
Die erste dieser beiden Alternativen ist für Variable, die keine Arrays sind, und die zweite für Arrays. Dabei muss cast-expression ein Zeiger sein, dessen Wert das Ergebnis eines new-Ausdrucks ist. Nach delete p ist der Wert von p unbestimmt und der Zugriff auf *p unzulässig. Falls p den Wert 0 hat, ist delete p wirkungslos.
Damit man mit dem verfügbaren Speicher sparsam umgeht, sollte man den Operator delete immer dann aufrufen, wenn eine mit new erzeugte Variable nicht mehr benötigt wird. Unnötig reservierter Speicher wird auch als Speicherleck (memory leak) bezeichnet. Ganz generell sollte man diese Regel beachten: Jede mit new erzeugte Variable sollte mit delete auch wieder freigegeben werden.
Die folgenden Beispiele zeigen, wie die in den letzten Beispielen reservierten Speicherbereiche wieder freigegeben werden.
1. Der Speicher für die unter 1. und 2. erzeugten Variablen wird folgendermaßen wieder freigegeben:
delete pi; delete pd;
2. Der Speicher für das unter 3. erzeugte Array wird freigegeben durch
delete[] p;
3. Es ist möglich, die falsche Form von delete zu verwenden, ohne dass der Compiler eine Warnung oder Fehlermeldung ausgibt:
delete[] pi; // Arrayform für Nicht-Array delete p; // Nicht-Arrayform für Array
Im C++-Standard ist explizit festgelegt, dass das Verhalten nach einem solchen falschen Aufruf undefiniert ist. Oben wurde empfohlen, einen Zeiger, der nicht auf reservierten Speicher zeigt, immer auf 0 (Null) zu setzen. Deshalb sollte man einen Zeiger nach delete immer auf 0 setzen, z.B. nach Beispiel 1:
pi=0; pd=0;
Stroustrup empfiehlt dafür eine Funktion wie destroy:
void destroy(int*& p) {//http://www.research.att.com/~bs/bs_faq2.html delete p; // delete[] für Zeiger auf dynamische Arrays p = 0; }
In dieser Function wird der Zeiger-Parameter als Referenz übergeben, da sich die Zuweisung von 0 auf das Argument auswirken soll. Die wichtigsten Unterschiede zwischen dynamisch erzeugten und „gewöhnlichen“ (vom Compiler erzeugten) Variablen sind:
1. Eine dynamisch erzeugte Variable hat im Unterschied zu einer gewöhnlichen Variablen keinen Namen und kann nur indirekt über einen Zeiger angesprochen werden.
Nach einem erfolgreichen Aufruf von p=new type enthält der Zeiger p die Adresse der Variablen. Falls p überschrieben und nicht anderweitig gespeichert wird, gibt es keine Möglichkeit mehr, sie anzusprechen, obwohl sie weiterhin existiert und Speicher belegt. Der für sie reservierte Speicher wird erst beim Ende des Programms wieder freigegeben.
Da zum Begriff “Variable” auch ihr Name gehört, ist eine “namenlose Variable” eigentlich widersprüchlich. Im C++-Standard wird „object“ als Oberbegriff für namenlose und benannte Variablen verwendet. Ein Objekt in diesem Sinn hat wie eine Variable einen Wert, eine Adresse und einen Datentyp, aber keinen Namen. Da der Begriff „Objekt“ aber auch oft für Variable eines Klassentyps (siehe Objekte) verwendet wird, wird zur Vermeidung von Verwechslungen auch der Begriff „namenlose Variable“ verwendet.
2. Der Name einer gewöhnlichen Variablen ist untrennbar mit reserviertem Speicher verbunden. Es ist nicht möglich, über einen solchen Namen nicht reservierten Speicher anzusprechen. Im Unterschied dazu existiert ein Zeiger p, über den eine dynamisch erzeugte Variable angesprochen wird, unabhängig von dieser Variablen. Ein Zugriff auf *p ist nur nach new und vor delete zulässig. Vor new p oder nach delete p ist der Wert von p unbestimmt und der Zugriff auf *p ein Fehler, der einen Programmabbruch zur Folge haben kann:
int* pi = new int(17); delete pi; ... *pi=18; // Jetzt knallts - oder vielleicht auch nicht?
3. Der Speicher für eine gewöhnliche Variable wird automatisch freigegeben, wenn der Gültigkeitsbereich der Variablen verlassen wird. Der Speicher für eine dynamisch erzeugte Variable muss dagegen mit genau einem Aufruf von delete wieder freigegeben werden.
– Falls delete überhaupt nicht aufgerufen wird, kann das eine Verschwendung von Speicher (Speicherleck, memory leak) sein. Falls in einer Schleife immer wieder Speicher reserviert und nicht mehr freigegeben wird, können die swap files immer größer werden und die Leistungsfähigkeit des Systems nachlassen.
– Ein zweifacher Aufruf von delete mit demselben, von Null verschiedenen Zeiger, ist ein Fehler, der einen Programmabsturz zur Folge haben kann.
Beispiel: Anweisungen wie
int* pi = new int(1); int* pj = new int(2);
zur Reservierung und
delete pi; delete pj;
zur Freigabe von Speicher sehen harmlos aus. Wenn dazwischen aber eine ebenso harmlos aussehende Zuweisung stattfindet,
pi = pj;
haben die beiden delete Anweisungen den Wert von pj als Operanden. Diese Zuweisung führt also dazu, dass pj doppelt und pi überhaupt nicht freigegeben wird.
4. Oben haben wir gesehen, dass eine Zuweisung von Zeigern zu Aliasing führen kann. Bei einer Zuweisung an einen Zeiger auf eine dynamisch erzeugte Variable besteht außerdem noch die Gefahr von Speicherlecks. Das kann z.B. mit den folgenden Strategien vermieden werden:
– Man vermeidet solche Zuweisungen. Diese Strategie wird von der smart pointer Klasse scoped_ptr der Boost-Bibliothek (siehe http://boost.org/) verfolgt.
– Der Speicher für diese Variable wird vorher freigegeben.
int* pi = new int(17); int* pj = new int(18); delete pi; // um ein Speicherleck zu vermeiden pi = pj;
Da anschließend zwei Zeiger pi und pj auf die mit new(18) erzeugte Variable *pj zeigen, muss darauf geachtet werden, dass der Speicher für diese Variable nicht freigegeben wird, solange über einen anderen Zeiger noch darauf zugegriffen werden kann. Diese Strategie wird von der smart pointer Klasse shared_ptr verfolgt.
– Der Speicher für eine dynamisch erzeugte Variable wird automatisch wieder freigegeben, wenn es keine Referenz mehr auf diese Variable gibt. Das wird als garbage collection bezeichnet. Garbage collection gehört allerdings noch nicht zum C++-Standard 2003. Es steht aber über die smart pointer Klasse shared_ptr der Boost-Bibliothek (siehe Abschnitt 3.12.5) sowie in speziellen Erweiterungen wie z.B. C++/CLI zur Verfügung. Es soll außerdem in den nächsten C++-Standard aufgenommen werden.
5. Der Operand von delete muss einen Wert haben, der das Ergebnis eines new-Ausdrucks ist. Wendet man delete auf einen anderen Ausdruck an, ist das außer bei einem Nullzeiger ein Fehler, der einen Programmabbruch zur Folge haben kann. Insbesondere ist es ein Fehler, delete auf einen Zeiger anzuwenden,
a) dem nie ein new-Ausdruck zugewiesen wurde.
b) der nach new verändert wurde.
c) der auf eine gewöhnliche Variable zeigt.
Beispiele: Der Aufruf von delete mit p1, p2 und p3 ist ein Fehler:
int* p1; // a) int* p2 = new int(1); p2++; // b) int i = 17; int* p3 = &i; // c)
6. Bei gewöhnlichen Variablen prüft der Compiler bei den meisten Operationen anhand des Datentyps, ob sie zulässig sind oder nicht. Ob für einen Zeiger delete aufgerufen werden muss oder nicht aufgerufen werden darf, ergibt sich dagegen nur aus dem bisherigen Ablauf des Programms.
Beispiel: Nach Anweisungen wie den folgenden ist es unmöglich, zu entscheiden, ob delete p aufgerufen werden muss oder nicht:
int i,x; int* p; if (x>0) p=&i; else p = new int;
Diese Beispiele zeigen, dass mit dynamisch erzeugten Variablen Fehler möglich sind, die mit „gewöhnlichen“ Variablen nicht vorkommen können. Zwar sehen die Anforderungen bei den einfachen Beispielen hier gar nicht so schwierig aus. Falls aber new und delete in verschiedenen Teilen des Quelltextes stehen und man nicht genau weiß, welche Anweisungen dazwischen ausgeführt werden, können sich leicht Fehler einschleichen, die nicht leicht zu finden sind.
– Deshalb sollte man „gewöhnliche“ Variablen möglichst immer vorziehen.
– Falls sich Zeiger nicht vermeiden lassen, sollte man das Programm immer so einfach gestalten, dass möglichst keine Unklarheiten aufkommen können.
– Smart pointer sind oft eine Alternative, die viele Probleme vermeidet.
Dynamisch erzeugte Variable bieten in der bisher verwendeten Form keine Vorteile gegenüber „gewöhnlichen“ Variablen. Trotzdem gibt es Situationen, in denen sie notwendig sind:
Hinweis: In der Programmiersprache C gibt es anstelle der Operatoren new und delete die Funktionen malloc bzw. free. Sie funktionieren im Prinzip genauso wie new und delete und können auch in C++ verwendet werden. Da sie aber mit void*-Zeigern arbeiten, sind sie fehleranfälliger. Deshalb sollte man immer new und delete gegenüber malloc bzw. free bevorzugen. Man kann in einem Programm sowohl malloc, new, free und delete verwenden. Speicher, der mit new reserviert wurde, sollte aber nie mit free freigeben werden. Dasselbe gilt auch für malloc und delete.