Operatoren new und delete

new/delete für einfache intrinsische Daten

Der Operator new reserviert zur Programmlaufzeit Speicher und erhält als Operand den Datentyp DTYP, für den Speicher reserviert werden soll. Als Ergebnis liefert new einen Zeiger auf den reservierten Speicherbereich zurück. Dieser zurückgelieferte Zeiger hat den Datentyp DTYP*. Durch die optionale Angabe eines Wertes in einer runden oder geschweiften Klammer kann der reservierte Speicher mit einem Wert initialisiert werden.

Kann new nicht den angeforderten Speicher reservieren, wird eine Ausnahme vom Typ bad_alloc ausgelöst und das Programm beendet. Was Ausnahmen sind und wie darauf reagiert werden kann folgt später im Kapitel Ausnahmebehandlung.

Die Freigabe des mittels new reservierten Speichers erfolgt mit dem delete Operator. Der delete  Operator erhält als Operand den von new zurückgelieferten Zeiger.

// Diese Beispiel ist mehr theoretischer Natur
// da es i.d.R. keinen Sinn macht fuer einfache
// Daten Speicher dynamisch anzufordern
int main()
{
    // Speicher für ein long-Datum reservieren
    long *lptr = new long;
    // Speicher für ein short-Datum reservieren
    // und mit dem Wert 10 initialisieren
    short *sptr = new short{10};
    // Oder besser mit auto
    auto sptr1 = new short{10};
    // ... hier mit den Daten arbeiten
    // und dann den Speicher wieder freigeben
    delete sptr1;
    delete sptr;
    delete lptr;
}

delete gibt nur den Speicher frei, verändert jedoch nicht den Inhalt des Zeigers, d.h., der Zeiger enthält weiterhin die bisherige Adresse. Ein Zugriff über diesen Zeiger auf den freigegebenen Speicher führt zu undefiniertem Verhalten der Anwendung.

new/delete für Felder

Um Speicher für ein Feld für intrinsische Daten zu reservieren, folgt nach dem Datentyp des Feldes in eckiger Klammern die Angabe der Feldgröße. Optional kann das Feld gleichzeitig mit Daten initialisiert werden, wobei die Feldgröße dann entfallen kann.

Bei der Freigabe des Speichers für ein Feld muss nach dem delete Operator zunächst eine leere eckige Klammer stehen und danach der von new zurückgelieferte Zeiger. Wird diese leere eckige Klammer vergessen, meldet der Compiler keinen Fehler! Warum bei der Freigabe des Speichers diese eckige Klammer trotzdem wichtig ist, erfahren Sie im nächsten Kapitel über dynamische Objekte. Vergessen Sie daher niemals die eckigen Klammern beim Löschen von Feldern.

int main()
{
    // Speicher für ein Feld mit 10 short reservieren
    auto ptr1 = new short[10];
    // Speicher für 5 long reservieren/initialisieren
    auto ptr2 = new long[] {1L,2L,3L,4L,5L};
    // ... Felder bearbeiten
    // Und Speicher auch wieder freigeben
    delete [] ptr1;
    delete [] ptr2;
}

Beachten Sie, dass bei der Reservierung des Speichers für Felder die Feldgröße in eckigen Klammern steht und nicht in runden oder geschweiften! Die Anweisung

auto ptr = new long(10);

reserviert Speicher für ein long-Datum das mit 10 initialisiert wird.

Die Reservierung von Speicher für mehrdimensionale Felder erfolgt in mehreren Schritten. Zuerst ist Speicher für ein Zeigerfeld zu reservieren. Dieses Zeigerfeld repräsentiert die 'Zeilen' (1. Dimension). Anschließend werden in diesem Zeigerfeld die Adressen der Spaltenfelder (2. Dimension) abgelegt.

Bei der Freigabe des Speichers für mehrdimensionale Felder ist die Reihenfolge der Reservierungen umzudrehen, d.h., es wird zuerst der Speicher für die Spaltenfelder freigegeben und zum Schluss der Speicher für das Zeigerfeld. Beachten Sie beim delete-Operator die leeren eckigen Klammern. Sie haben es hier mit Feldern zu tun!

#include <cstdlib>

// Feld mit 3x2 short-Zahlen reservieren
constexpr int ROW=3;
constexpr int COLUMN=2;

// (1): Zeiger auf Zeiger definieren
short **ppArray;

int main()
{
   // (2): Zeigerfeld für 3 short-Zeiger reservieren
   // Alternativ(1+2): auto ppArray = new short*[ROW]
   ppArray = new short*[ROW];

   // (3): Spaltenfelder reservieren
   for (auto i=0; i<ROW; i++)
      ppArray[i] = new short[COLUMN];
   
   // Zugriff auf Feld wie üblich
   // Feld beliebig fuellen (2x for-Schleife)
   for (auto row=0; row<ROW; row++)
      for (auto col=0; col<COLUMN; col++)
          ppArray[row][col] = std::rand()%100;

    // (4) Zuerst Spaltenfelder freigeben
    for (auto i=0; i<ROW; i++)
        delete [] ppArray[i];
    // (5) Und Zeigerfeld freigeben
    delete [] ppArray;

}

Es ist erlaubt, den delete-Operator mit einem nullptr aufzurufen oder mit einem Zeiger, dessen Inhalt 0 ist (aus historischen Gründen). In diesem Fall führt der delete Operator nichts aus.

Zur sequentiellen Bearbeitung von dynamischen Feldern kann keine range-for-Schleife eingesetzt werden.

Übungen

newdel_01:

Erzeugen Sie aus einer einzulesenden Anzahl Zufallszahlen im Bereich 0 bis 99. Die Zufallszahlen sind in einem Feld abzulegen. Berechnen Sie den Mittelwert der Zufallszahlen und geben diesen aus.

Aus wie vielen Zufallszahlen soll
der Mittelwert berechnet werden? 1000
Der Mittelwert ist: 48.255

newdel_02:

Implementieren Sie einen Stack zur Ablage von unsigned short-Werten. Ein Stack ist ein Bereich zum temporären Sichern von Daten, d.h. auf einem Stack können Daten abgelegt und später wieder ausgelesen werden. Hierbei gilt, dass das zuletzt abgelegte Datum als Erstes wieder ausgelesen wird.

Die Anzahl der maximal abzulegenden Werte ist einzulesen, soll aber im Bereich 5...20 liegen.

Füllen Sie dann Stack solange mit Zufallswerten bis er belegt ist. Geben Sie die Werte zur Kontrolle dabei aus.

Holen Sie dann die letzten 3 Werte wieder vom Stack und geben diese ebenfalls aus.

Verwenden Sie keine Klasse für den Stack, sondern definieren diesen direkt in der main() Funktion.

Wie gross soll der Stack sein (5..20)? 7
Werte auf dem Stack:
41, 67, 34, 0, 69, 24, 78,
Hole letzte 3 Werte vom Stack
78, 24, 69,
Stackbereich auch wieder freigeben!