Explizites Speichermanagement in C++ besitzt eine hohe Komplexität aber auch eine mindestens so große Funktionalität. Leider ist diese spezielle Domäne von C++ weitgehend unbekannt. So lassen sich mit ihr Objekte direkt in einem statischen Speicher, einem vorreserviertem Bereich oder auch einem Speicherpool erzeugen. Funktionalität, die für sicherheitskritische Applikationen insbesondere in der embedded Welt einen entscheidenden Mehrwert liefert. Doch vor der Kür steht die Pflicht. Daher werde ich in diesem Artikel einen Überblick geben, bevor ich in weiteren Artikeln auf die Details eingehen.
Zur Speicherallokation kommt new bzw. new[] zum Einsatz, zur Speicherfreigabe delete bzw. delete[]. Das ist natürlich noch nicht die ganze Geschichte.
Speicherallokation
new
Mit dem Operator new ist es möglich, Speicher für eine Instanz eines Typs dynamisch zu allozieren.
int* i= new int;
double* x= new double(10.0);
Circle* c= new Circle;
Point* p= new Point(1.0, 2.0);
Die in runden Klammern spezifizierten Argumente sind die Argumente für den Konstruktor. Das Ergebnis des new Befehls ist ein Zeiger des passenden Typs. Die Initialisierung geschieht nach der Allokation des Speichers. Diese zwei Schritte macht sich placement-new zu Nutzen.
new[]
Im Gegensatz zu new legt new[] ein C-Array von Objekten an. Die Klasse der zu allozierenden Objekte benötigt einen Default-Konstruktor.
double* da= new double[5];
Circle* ca= new Circle[8];
Placement-new
Placement-new wird gerne verwendet, um ein Objekt in einem vor reservierten Speicherbereich zu instanziieren. Darüber hinaus kann placement-new global und für eigene Datentypen überladen werden. Das ist der wichtige Mehrwert, den C++ liefert.
1
2
3
4
5
|
char* memory= new char[sizeof(Account)];
Account* a= new(memory) Account;
char* memory2= new char[5*sizeof(Account)];
Account* b= new(memory2) Account[5];
|
Placement-new benötigt ein zusätzliches Argument (Zeile 2 und 5). Die Zeile 1 und 4 führt dazu, dass Operator new(sizeof(Account),memory) verwendet wird. Oder anders ausgedrückt. Das Objekt a wird in dem Speicherbereich memory instanziiert. Entsprechende Aussage gilt natürlich auch für das C-Array b.
Fehlgeschlagene Allokation
Schlägt eine Speicherallokation fehl, lösen new und new[] eine std::bad_alloc-Ausnahme aus. Dies ist aber oft nicht das gewünschte Verhalten. Statt dessen kann placement-new mit der Konstante std::nothrow aufgerufen werden: char* c new(std::nothrow) char[10]. Dieser Aufruf bewirkt, dass im Fehlerfall ein Null-Zeiger zurückgegeben wird.
New Handler
Für den Fall einer fehlgeschlagenen Speicherallokation erlaubt std::set_new_handler einen eigenen Handler zu verwenden. std::set_new_handler gibt dabei den alten Handler zurück und erwartet eine aufrufbare Einheit, die kein Argument annimmt und kein Wert zurückgibt. Mit std::get_new_handler lässt sich der Handler zurückgeben.
Mit eigenen Handler lassen sich individuelle Strategien für fehlgeschlagenen Speicherallokationen umsetzen:
- stelle mehr Speicher zur Verfügung
- beendet das Programm durch std::terminate
- löse eine Ausnahme vom Typ std::bad_alloc aus
Speicherfreigabe
delete
Ein zuvor mittels new angeforderter Speicher wir mit delete wieder freigegeben.
Circle* c= new Circle;
...
delete c;
Die jeweiligen Destruktoren des Objektes und gegebenenfalls Destruktoren der Basisklassen werden automatisch aufgerufen. Falls der Destruktor der Basisklasse virtuell ist, kann das Objekt mit dem Zeiger oder Referenz auf die Basisklasse destruiert werden.
Nachdem der Speicher freigegeben worden ist, ist der Zugriff des Objekts undefiniert. Der Zeiger kann aber noch auf ein anderes Objekt umgebogen werden.
delete[]
Für die Freigabe eines C-Arrays, das mit new[] alloziert wurde, ist der Operator delete[] zuständig.
Circle* ca= new Circle[8];
...
delete[] ca;
Durch den Aufruf von delete[] werden alle Destruktoren aller Objekte aufgerufen.
Die Freigabe eines C-Arrays mit delete stellt hingegen undefiniertes Verhalten dar.
Placement-delete
In Analogie zu placement-new kann auch placement-delete implementiert werden. Die Besonderheit ist aber, dass die C++-Laufzeit nicht automatisch placement-delete aufruft. Daher liegt dies in der Verantwortung des Programmierers.
Eine typische Strategie besteht darin, im ersten Schritt den Destruktor explizit aufzurufen und im zweiten Schritt den Speicher mit placement-delete freizugeben.
char* memory= new char[sizeof(Account)];
Account* a= new(memory) Account; // placement new
...
a->~Account(); // destructor
operator delete(a,memory); // placement delete
Die entsprechenden Aussagen und Strategie gelten natürlich auch beim Einsatz von placement-delete für ein C-Array.
Wie geht's weiter?
Der Weg ist vorgezeichnet. Im nächsten Artikel schaue ich mir genauer die Speicherallokation mit Operator new und new[] und natürlich auch placement-new an.
Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library". Hole dir dein E-Book. Unterstütze meinen Blog.
Weiterlesen...