C -Programmierung/ Speicherverwaltung/ Stack und Heap
C unterteilt den verfügbaren Speicher in vier Bereiche. Diese sind der
- Programmspeicher,
- globale Speicher für globale Variable,
- Haldenspeicher für die dynamische Speicherverwaltung, und
- Stapelspeicher (statische Speicherverwaltung).[1]
Der Programmspeicher beinhaltet, wie der Name schon verrät, das Programm. In zahlreichen Sprachen ist er strikt vom Datenspeicher getrennt. Manche Sprachen erlauben aus programmiertechnischen Gründen keine globalen Variablen. Bei C sind diese zwar erlaubt, es wird aber zwischen Programm- und Datenspeicher grundsätzlich unterschieden.
Neben dem Speicher für globale Variable bleiben noch zwei Bereiche für die Daten. Einer dieser Bereiche wird als Stapelspeicher oder kurz Stapel (stack) bezeichnet und wir haben ihn schon häufig in Anspruch genommen. Den zweiten Speicherbereich bezeichnet man als Haldenspeicher oder kurz als Halde (heap). Er dient der dynamischen Speicherverwaltung und wird in diesem Abschnitt umfassend behandelt.
Für den Stapelspeicher gilt immer: Was zuletzt angefordert wurde, muss auch als erstes wieder freigegeben werden (LIFO: Last In – First Out). Wenn Sie innerhalb eines Blocks {;;;} also Variablen anlegen, werden diese auf dem Stack angelegt. Am Ende des Blocks verliert die Variable ihre Gültigkeit und der Speicher wird wieder freigegeben. Wenn Sie nun eine Funktion aufrufen, wird die aktuelle Programmadresse (also die Stelle im Programm, an der die Funktion aufgerufen wird, die sog. „Rücksprungadresse“) auf dem Stapel abgelegt. Innerhalb der Funktionen werden möglicherweise Variablen angelegt, die wiederum auf dem Stapel landen. Dass dies so geschehen soll, wird vom Compiler zur Übersetzungszeit festgelegt und ist somit eine statische Speicherverwaltung. Am Ende der Funktion werden die Speicherbereiche der Variablen wieder freigegeben und das Programm springt zur Rücksprungadresse, die jetzt wieder oben auf dem Stapel liegt. Somit befindet es sich jetzt wieder an der Stelle, an der die Funktion aufgerufen wurde.
Speicher aus der Halde wird nicht geordnet vergeben. Sie können ihn zu einem beliebigen Zeitpunkt anfordern und müssen ihn auch selbst wieder freigeben. Somit kann innerhalb einer Funktion Haldenspeicher angefordert, und nach Beendigung der Funktion ein Objekt, das auf der Halde liegt, weiterhin genutzt werden. Es wird also nicht mit Beendigung der Funktion ungültig. Versucht ein Objekt so, Speicher für sich zu reservieren, wird dieser im Rahmen der sogenannten dynamischen Speicherverwaltung zur Laufzeit festgelegt.
In den folgenden Kapiteln lernen Sie in erster Linie, wie man in C mit Haldenspeicher arbeitet.
Objekte Erstellen und Zerstören
[Bearbeiten]Im Stack (deutsch: Stapel)
[Bearbeiten]Effektive Objekte können nur für den aktuellen Gültigkeitsbereich auf dem sog. Stack erstellt werden. Der Stapelspeicher ist ein Speicherbereich für lokale Variablen eines Moduls (statische Speicherverwaltung). Beim Verlassen eines Gültigkeitsbereichs werden diese Objekte automatisch zerstört. Alle vorigen Beispiele in diesem Abschnitt zeigen, wie Objekte auf dem Stack erstellt und zerstört werden. Größere Objekte wie große Speicherblöcke sollten nur in Beispielanwendungen auf dem Stack erstellt werden, denn dieser Bereich ist stark begrenzt und ist ausschließlich für lokale und temporäre Daten gedacht. Moderne Übersetzer begrenzen diesen Bereich auf 1 Megabyte. Wenn Sie größere Objekte auf den Stack legen wollen, müssen Sie die maximale Stackgröße modifizieren! Tun Sie dies nicht, erhalten Sie höchst bemerkenswerte Meldungen von Laufzeitumgebung, Betriebssystem oder Programmabbrüchen.
Auf dem Heap (deutsch: Halde)
[Bearbeiten]Effektive Objekte können dynamisch und permanent bis zum Ende der Laufzeit des Moduls erstellt werden. Dies erfolgt im sog. Heap. Der Heap entspricht meistens dem nicht vorgespeicherten Datensegment für das gesamte Programm (dynamische Speicherverwaltung). Dazu verwendet man den Operator new
. Wenn ein Objekt nicht mehr benötigt wird, muss es bei dieser Variante manuell zerstört werden und zwar mit dem Operator delete
. Weiterführende Konzepte wie Smart-Pointer können das Zerstören beim Verlassen von Gültigkeitsbereichen automatisieren. Diese Operatoren kann man auch für Felder verwenden. Dann muss allerdings bei der Zerstörung der Operator delete []
verwendet werden.
Der Heap hat den eklatanten Vorteil, dass die Grenzen des zuteilbaren Speichers nur vom Betriebssystem und der physikalischen Speichermenge gezogen werden und nicht von Compiler- und Linkereinstellungen. Ein weiterer Vorteil ist, dass alle Elemente einer Klasse dann auch auf dem Heap liegen.
Wir verwenden Klasse 'a' aus vorigem Beispiel:
int main(){
A *pObjekt(0); // Zeiger auf ein A-Objekt
pObjekt = new A; // Instanziieren auf dem Heap, Standardkonstruktor verwenden
delete pObjekt; // Zerstören
char *pszMemory = new char[0x100000]; // 1 Megabyte auf dem Heap allozieren
delete [] pszMemory; // Speicherblock wieder freigeben
A *ar_Objekte = new A[50]; // 50 Objekte von A anlegen
delete [] ar_Objekte;
return 0;
}
Weitere Optionen zur Verwendung von new
, delete
, new []
und delete []
gibt es auch.
Referenzen
[Bearbeiten]