C-Programmierung: Speicherverwaltung
Die Daten, mit denen ein Programm arbeitet, müssen während der Laufzeit an einem bestimmten Ort der Computer-Hardware abgelegt und zugreifbar sein. Die Speicherverwaltung bestimmt, wo bestimmte Daten abgelegt werden, und wer (welche Programme, Programmteile) wie (nur lesen oder auch schreiben) darauf zugreifen darf. Zudem unterscheidet man Speicher auch danach, wann die Zuordnung eines Speicherortes überhaupt stattfindet. Die Speicherverwaltung wird in erster Linie durch die Deklaration einer Variablen (oder Konstanten) beeinflusst, aber auch durch Pragmas und durch Laufzeit-Allozierung, üblicherweise malloc oder calloc.
Ort und Art der Speicherreservierung (Speicherklasse)
[Bearbeiten]Zum Teil bestimmt der Ort eines Speichers die Zugriffsmöglichkeiten und -geschwindigkeiten, zum Teil wird der Zugriff aber auch von Compiler, Betriebssystem und Hardware kontrolliert.
Speicherorte
[Bearbeiten]Mögliche physikalische Speicherorte sind in erster Linie die Register der CPU und der Arbeitsspeicher.
Um eine Variable explizit in einem Register abzulegen, deklariert man eine Variable unter der Speicherklasse register, z.B.:
register int var;
Von dieser Möglichkeit sollte man allerdings, wenn überhaupt, nur äußerst selten Gebrauch machen, da eine CPU nur wenige Register besitzt, und einige von diesen stets für die Abarbeitung von Maschinenbefehlen benötigt werden. Die meisten Compiler verfügen zudem über Optimierungs-Algorithmen, die Variablen in der Regel dann in Registern ablegen, wenn es am sinnvollsten ist.
Die Ablage im Arbeitsspeicher kann grundsätzlich in zwei verschiedenen Bereichen erfolgen.
Zum einen innerhalb einer Funktion, die Variable hat dann zur Ausführungszeit der Funktion eine Position im Stack oder wird vom Optimierungs-Algorithmus in einem Register platziert. Bei erneutem Aufruf der Funktion hat die Variable dann nicht den gleichen Wert, wie zum Abschluss des letzten Aufrufs. Bei rekursivem Aufruf erhält sie einen neuen, eigenen Speicherplatz, auch mit einem anderen Wert. Deklariert man eine Variable innerhalb einer Funktion ohne weitere Angaben zur Speicherklasse innerhalb eines Funktionskörpers, so gehört sie der Funktion an, z.B:
int fun(int var) {
int var;
}
Zum anderen im allgemeinen Bereich des Arbeitsspeichers, außerhalb des Stacks. Dies erreicht man, indem man die Variable entweder außerhalb von Funktionskörpern, oder innerhalb unter der Speicherklasse static deklariert:
int fun(int var) {
static int var;
}
In Bezug auf Funktionen hat static eine andere Bedeutung, siehe ebenda. Ebenfalls im allgemeinen Arbeitsspeicher landen Variablen, deren Speicherort zur Laufzeit alloziert wird, s.u.
Insbesondere bei eingebetteten Systemen gibt es oft unterschiedliche Bereiche des allgemeinen Adressbereichs des Arbeitsspeichers, hauptsächlich unterschieden nach RAM und ROM. Ob eine Variable in direktem Zugriff nur gelesen oder auch geschrieben werden kann, hängt dann also vom Speicherort ab. Der Speicherort einer Variable wird hier durch zusätzliche Compiler-Direktiven, Pragmas, deklariert, deren Syntax sich zwischen den jeweiligen Compilern stark unterscheidet.
Zugriffsverwaltung
[Bearbeiten]Zeitpunkt der Speicherreservierung
[Bearbeiten]Zum Zeitpunkt des Kompilierens
[Bearbeiten]Zur Ladezeit
[Bearbeiten]Während der Laufzeit
[Bearbeiten]Wenn Speicher für Variablen benötigt wird, z.B. eine skalare Variable mit
int var;
oder eine Feld-Variable mit
int array[10];
deklariert werden, wird auch automatisch Speicher auf dem Stack reserviert.
Wenn jedoch die Größe des benötigten Speichers zum Zeitpunkt des Kompilierens noch nicht feststeht, muss der Speicher dynamisch reserviert werden.
Dies geschieht meist mit Hilfe der Funktionen malloc() oder calloc() aus dem Header stdlib.h, der man die Anzahl der benötigten Byte als Parameter übergibt. Die Funktion gibt danach einen void-Zeiger auf den reservierten Speicherbereich zurück, den man in den gewünschten Typ casten kann. Die Anzahl der benötigten Bytes für einen Datentyp erhält man mit Hilfe des sizeof() -Operators.
Beispiel:
int *zeiger;
zeiger = (int *) malloc(sizeof(*zeiger) * 10); /* Reserviert Speicher für 10 Integer-Variablen
und lässt 'zeiger' auf den Speicherbereich zeigen. */
Nach dem malloc() sollte man testen, ob der Rückgabewert NULL ist. Im Erfolgsfall wird malloc() einen Wert ungleich NULL zurückgeben. Sollte der Wert aber NULL sein ist malloc() gescheitert und das System hat nicht genügend Speicher allokieren können. Versucht man, auf diesen Bereich zu schreiben, hat dies ein undefiniertes Verhalten des Systems zur Folge. Folgendes Beispiel zeigt, wie man mit Hilfe einer Abfrage diese Falle umgehen kann:
#include <stdlib.h>
#include <stdio.h>
int *zeiger;
zeiger = (int *) malloc(sizeof(*zeiger) * 10); // Speicher anfordern
if (zeiger == NULL) {
perror("Nicht genug Speicher vorhanden."); // Fehler ausgeben
exit(EXIT_FAILURE); // Programm mit Fehlercode abbrechen
}
free(zeiger); // Speicher wieder freigeben
Wenn der Speicher nicht mehr benötigt wird, muss er mit der Funktion free() freigegeben werden, indem man als Parameter den Zeiger auf den Speicherbereich übergibt.
free(zeiger); // Gibt den Speicher wieder frei
Wichtig: Nach dem free steht der Speicher nicht mehr zur Verfügung, und jeder Zugriff auf diesen Speicher führt zu undefiniertem Verhalten. Dies gilt auch, wenn man versucht, einen bereits freigegebenen Speicherbereich nochmal freizugeben. Auch ein free() auf einen Speicher, der nicht dynamisch verwaltet wird, führt zu einem Fehler. Einzig ein free() auf einen NULL-Zeiger ist möglich, da hier der ISO-Standard ISO9899:1999 sagt, dass dieses keine Auswirkungen haben darf. Siehe dazu folgendes Beispiel:
int *zeiger;
int *zeiger2;
int *zeiger3;
int array[10];
zeiger = (int *) malloc(sizeof(*zeiger) * 10); // Speicher anfordern
zeiger2 = zeiger;
zeiger3 = zeiger++;
free(zeiger); // geht noch gut
free(zeiger2); // FEHLER: DER BEREICH IST SCHON FREIGEGEBEN
free(zeiger3); /* undefiniertes Verhalten, wenn der Bereich
nicht schon freigegeben worden wäre. So ist
es ein FEHLER */
free(array); // FEHLER: KEIN DYNAMISCHER SPEICHER
free(NULL); // KEIN FEHLER, ist laut Standard erlaubt