Stack und Heap

Abbildung Stack und Heap

Im allgemeinen bezeichnen Stack ("Stapel") und Heap ("Haufen") Datenstrukturen mit ihren ganz speziellen Eigenschaften. Dieser Beitrag bezieht sich aber auf die konkrete Verwendung für die Bereitstellung von Speicher für ein ausführbares Programm. Die folgenden Grundsätze gelten für die meisten aktuellen Programmiersprachen.

Stack und Heap sind Teile des Arbeitsspeichers

Im Kontext der Speicherverwaltung auf Programmebene handelt es sich also sowohl beim Stack als auch beim Heap um Teile des Arbeitsspeichers, die vom Betriebssystem dem ausführenden Programm zur Verfügung gestellt werden. Um genauer zu sein handelt es sich um einen Bereich des virtuellen Speichers. Dadurch ist für den Prozess der tatsächliche physikalische Speicherort eines Objekts nicht bekannt. Die Daten können im Hintergrund irgendwo im Arbeitsspeicher liegen oder sogar auf die Festplatte ausgelagert sein.

Der Stack

Der Name deutet an, dass die Daten hier "aufeinander" liegen. Damit ist gemeint, dass neue Daten immer nur oben drauf gelegt werden können. Wenn die Daten wieder freigegeben werden, werden Sie von oben nach unten wieder entfernt. Dieses Prinzip nennt sich auch LIFO ("Last in, First out"). Der Stack kann, bedingt durch seine Struktur, sehr effizient verwaltet werden, weshalb Stack-Operationen sehr schnell sind.

Jeder Thread eines Programmes erhält für den Stack einen eigenen Speicherbereich mit fixer Größe zugewiesen. Darauf werden Informationen zum Programmablauf (z.B. Funktionsparameter) und lokale Variablen gespeichert. Beim Anlegen neuer lokaler Variablen wächst der Stack an und beim Verlassen des Sichtbarkeitsbereichs ("Scope") schrumpft er wieder und der Speicher wird automatisch aufgeräumt. Typische Stackgrößen variieren zwischen 64 KB und 8 MB. Einstellen (auch wenn das im Normalfall nicht nötig ist) kann man die Stackgröße meist über die Entwicklungsumgebung, aber auch das Betriebssystem kann natürlich Einfluss nehmen.

    {
        // Create a new object on the stack
        CExampleClassBase myObject;

        // Use '.'-operator for function calls through variable
        myObject.Print();

    } // end of scope, stack variables will be deleted, myObject's destructor will be called

Dieses Beispiel zeigt, wie in C++ auf dem Stack ein Objekt angelegt wird. Die geschwungenen Klammern definieren einen Sichtbarkeitsbereich, innerhalb dessen die Stack-Variable gültig bleibt. Beim Verlassen des Sichtbarkeitsbereichs wird das Objekt zerstört (der Destruktor wird aufgerufen) und der Stack wieder geschrumpft (d.h. der Speicher wird wieder freigegeben).

Nochmal zusammengefasst die Eigenschaften des Stacks:

  • Begrenzte Größe
  • LIFO Datenstruktur (die zuletzt angelegten Daten werden als erstes wieder freigegeben, deshalb auch "Stapel")
  • Wächst und schrumpft mit dem Programmverlauf
  • Wird verwendet für lokale Variablen und Funktionsparameter
  • Kein explizites Freigeben des Speichers nötig
  • Das Ablegen und Entfernen von Elementen ist sehr effizient

Der Heap

Der Heap ist nicht so strukturiert wie der Stack. Du kannst ihn dir tatsächlich als Haufen vorstellen, auf dem jede Menge Platz ist. Während der Stack nämlich von der Größe her stark begrenzt ist, kann der Heap anwachsen bis die Speichergrenze auf Prozessebene erreicht ist. Dafür ist der Heap aber intern nicht so einfach zu verwalten, was ihn langsamer als den Stack macht. Auf dem Heap angelegter Speicher muss auch explizit wieder freigegeben werden (durch den Programmierer oder z.B. den Garbage Collector, je nach Programmiersprache).

Für den Zugriff auf den Heap werden Zeiger verwendet auch wenn das nicht immer direkt ersichtlich ist. Java und C# verwenden zum Beispiel Referenzen für Objekte die auf dem Heap liegen. Im Hintergrund muss aber natürlich trotzdem mit Zeigern gearbeitet werden, welche die Speicheradresse des Objektes beinhalten.

Da auf dem Heap angelegte Objekte nicht auf den lokalen Sichtbarkeitsbereich beschränkt sind, kann global darauf zugegriffen werden (sofern ein Zeiger oder eine Referenz vorhanden ist).

    // Create a new object on the heap and retrieve pointer to it
    CExampleClassBase* myObject = new CExampleClassDerived();

    // Use '->'-operator for function calls through pointer
    myObject->Print();

    delete myObject;
    myObject = NULL; // NULL maybe replaced by 'nullptr' since C++11

Hier wird ein Objekt in C++ mit Hilfe des new-Operators auf dem Heap angelegt. Nach Verwendung muss das Objekt ausdrücklich mit delete wieder freigegeben werden. Das zurücksetzen des Pointers beugt einem Zugriff auf bereits freigegebenen Speicher vor, denn delete gibt zwar den Speicher frei, setzt aber den Zeiger nicht auf NULL zurück. Wenn das delete erst zu einem späteren Zeitpunkt ausgeführt wird (z.B. beim Beenden des Programms), kann der Zeiger beliebig an andere Objekte verteilt werden, die dann ebenfalls Zugriff auf das Objekt erhalten.

Die Eigenschaften des Heaps lassen sich also wie folgt zusammenfassen:

  • Der Heap kann innerhalb der Prozessgrenze beliebig groß werden
  • Anlegen und freigeben von Objekten ist vergleichsweise langsam
  • Auf dem Heap angelegte Objekte können global verfügbar gemacht werden
  • In Programmiersprachen ohne Garbage Collector muss der Speicher manuell freigegeben werden, wenn er nicht mehr benötigt wird

Anwendung

In manchen Programmiersprachen (z.B. Java) hat man keinen direkten Einfluss auf die Verwendung von Stack oder Heap. In C++ besteht diese Möglichkeit aber schon. Es können zum Beispiel Objekte auf dem Stack angelegt werden. Das macht Sinn, wenn das Objekt nur kurzzeitig, also innerhalb des Sichtbarkeitsbereichs, gebraucht wird. Meistens gilt aber in C++ auch, dass man Objekte mit dem new-Operator auf dem Heap anlegt und für den Rest den Stack verwendet. Wenn du vergisst, ein mit new angelegtes Objekt wieder zu löschen, entsteht ein Speicherleck ("Memory Leak").

Programmierung vs. Softwareentwicklung

Die Programmierung ist ein Teilbereich der Softwareentwicklung oder genauer gesagt des Softwareentwicklungsprozesses, der durch das Vorgehensmodel

C++ eignet sich gut für Programmieranfänger

Es gibt jede Menge Programmiersprachen. Viele davon sind sich ähnlich, manche sind sehr eigenartig.

Software Wiederverwendung

Ein sehr schöner Aspekt an der Programmierung ist, dass man theoretisch keine Aufgabe oder kein Problem mehr als einmal lösen muss.

Copyright © 2014 www.lerneprogrammieren.com