Es gibt viele Strategien, Speicher anzufordern. Programmiersprachen wie Python oder Java fordern ihren Speicher auf dem Heap zur Laufzeit des Programms an. C und C++ kennt natürlich auch den Heap, benutzt aber bevorzugt den Stack. Doch diese Strategie sind bei weitem noch nicht alle. Speicher lässt sich natürlich auch zur Startzeit des Programms als fester Block oder Pool von Blöcken vorallokieren und zur Laufzeit verwenden. Da stellt sich mir natürlich die Frage. Was sind die Vorteile dieser verschiedenen Strategien, Speicher anzufordern?
Zuerst will ich vier typische Speicherstrategien vorstellen.
Strategien für das Anfordern von Speicher
Dynamische Allokation
Dynamische Allokation oder auch variable allocation ist natürlich jedem Programmierer wohl bekannt. Operationen wie new in C++ oder malloc in C fordern den Speicher genau dann an, wenn er benötigt wird. Hingegen geben Aufrufe wie delete in C++ oder free in C den Speicher wieder frei, wenn er nicht mehr benötigt wird.
int* myHeapInt= new int(5);
delete myHeapInt;
Der feine Unterschied in der Implementierung besteht darin, ob der Speicher automatisch freigegeben wird oder explizit freigegeben werden muss. Sprachen wie Java oder Python kennen Garbage Collection, Sprachen wie C++ und C nicht.
Dynamische Speicherallokation besitzt einige Vorteile. So ist die Allokation dynamisch und lässt sich automatisch an die Bedürfnisse des Programms anpassen. Natürlich besteht bei der dynamischen Speicherallokation immer die Gefahr der Speicher Fragmentierung. Darüber hinaus kann eine dynamische Speicheranforderung fehlschlagen oder auch viel zu lange benötigen. Gerade die letzten zwei Punkte sprechen gegen die dynamische Speicherallokation in hoch sicherheitskritischer Software, die ein genaues Timing fordert.
Bei den Smart Pointern in C++ wird der dynamisch angeforderte Speicher durch Objekte auf dem Stack automatisch verwaltet.
Stack Allokation
Stack Allokation ist auch unter dem Namen memory discard bekannt. Die zentrale Idee der Stack Allokation ist, dass die Objekte in einem temporären Bereich angelegt und automatisch wieder freigegeben werden, wenn der temporäre Bereich verlassen wird. Damit kümmert sich die C++-Laufzeit automatisch um den Lebenszyklus seiner Objekte. Das sind typischerweise Bereiche wie Funktionen, Objekte oder Programmschleifen, können aber auch künstliche Bereiche sein, die mit zwei geschweiften Klammern definiert werden.
Ein besonderes schönes Beispiel für Stack Allokation sind die Smart Pointer std::unique_ptr und std::shared_ptr. Diese werden auf dem Stack angelegt um Objekte, die auf dem Heap angelegt sind, automatisch zu verwalten.
Was sind nun die Vorteile der Stack Allokation. Zuerst einmal ist die Speicherverwaltung sehr einfach, da die C++-Laufzeit die ganze Arbeit übernimmt. Weiter steht auf der Habenseite, dass das zeitliche Verhalten der Speicheranforderung vollkommen deterministisch ist. Es gibt aber auch große Nachteile. Zum einen ist In der Regel der Stack kleiner als der Heap, zum anderen benötigt das Objekt eine längere Lebenszeit als der Bereich, in dem sie erzeugt wurde. Darüber hinaus unterstützt der C++ Standard keine dynamische Allokation auf dem Stack.
Statische Allokation
Für die statische Allokation hat sich auch der Name fixed allocation eingebürgert. Die zentrale Idee ist es, dass zur Laufzeit benötigter Speicher bereits beim Starten des Programms angefordert und in der Regel nicht mehr freigegeben wird. Das setzt natürlich voraus, dass der Speicherbedarf des Programms vorausberechnet werden kann.
char* memory= new char[sizeof(Account)];
Account* a= new(memory) Account;
char* memory2= new char[5*sizeof(Account)];
Account* b= new(memory2) Account[5];
So werden in dem kleinen Beispiel, die Objekte a und b direkt in dem Speicher konstruiert, den memory und memory2 bereits allokiert haben.
Was sind die Vor- und Nachteile der statischen Speicherallokation? In harten Echtzeitanwendungen, in denen die zeitliche Variation von dynamischer Speicheranforderung keine Option ist, wird die statische Allokation, die exakt vorhersagbar ist, sehr gerne eingesetzt. Weiter spricht für die statische Allokation, dass die Fragmentation des Speichers sehr gering ist. Natürlich gibt es auch Nachteile. Zum einen ist es oft nicht möglich, die Speicheranforderungen der Applikation vorherzusagen oder auch auf besondere Speicheranforderungen dynamisch einzugehen, darüber hinaus kann sich die Startzeit des Programms verlängern.
Memory Pool
Memory pool oder auch pooled allocation verbindet die Vorhersagbarkeit der statischen Allokation mit der Flexibilität der dynamischen Allokation. Ähnlich wie bei der statischen Allokation wird bei dem memory pool der Speicher zu Startzeit des Programms schon angefordert. Im Gegensatz dazu wird aber typischerweise ein pool of objects angefordert und verwendet. Zur Laufzeit bedient sich die Applikation aus diesem pool und gibt die Objekte auch wieder an den pool zurück. Gibt es nicht nur eine, sondern mehrere typische Größen von Objekten, liegt es natürlich auf der Hand, auch mehrere memory pools anzulegen.
Was sind die Vor- und Nachteile? Die Vorteile sind denen der statischen Allokation sehr ähnlich. Zum einen wird die Fragmentierung des Speichers unterbunden, zum anderen ist das zeitliche Verhalten der Speicherallokation und -deallokation vorhersagbar. Gegenüber der statischen Allokation zeichnet die memory pool Allokation aus, dass sie deutlich mehr Flexibilität und Komfort bietet. Zum einen, da pools verschiedener Größe verwendet werden können, zum andern, da der Speicher in den Pool zurückgegeben wird. Natürlich ist memory pool die anspruchsvollste aller der hier vorgestellten Techniken.
Wie geht's weiter?
Manchmal könnte man Python oder Java beneiden. Sie verwenden die dynamische Speicherallokation im Zusammenhang mit einem Garbage Collector und alles ist gut. Alles ist gut? Alle vier hier vorgestellten Techniken sind natürlich in C oder C++ im Einsatz und besitzen ihre besonderen Vorteile. Im nächsten Artikel dieser Serie will ich mir genauer anschauen, worin sich die vorgestellten Techniken unterscheiden. Mich interessieren vor allem die Punkte Vorhersagbarkeit, Skalierung, interne und externe Fragmentation und memory exhaustion.
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...