Lokale Variablen leben auf dem Stack und verschwinden am Funktionsende. Daten, die länger leben oder deren Größe erst zur Laufzeit feststeht, liegen auf dem Heap. Dort reservierst und gibst du Speicher selbst frei.
| Stack | Heap | |
|---|---|---|
| Verwaltung | automatisch | von Hand (malloc/free) |
| Größe | klein (oft 1 bis 8 MB) | groß (Arbeitsspeicher) |
| Lebensdauer | bis Blockende | bis free |
| Geschwindigkeit | sehr schnell | langsamer |
malloc, calloc, realloc, free
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n = 5;
int *a = malloc(n * sizeof *a); // n ints, nicht initialisiert
if (a == NULL) { fprintf(stderr, "kein Speicher\n"); return 1; }
for (int i = 0; i < n; i++) a[i] = i * i;
int *b = calloc(n, sizeof *b); // n ints, alle 0
printf("%d %d %d\n", a[4], b[0], b[4]);
int *c = realloc(a, 10 * sizeof *a); // Größe ändern (kann verschieben!)
if (c == NULL) { free(a); free(b); return 1; }
a = c;
a[9] = 81;
printf("%d %d\n", a[4], a[9]);
free(a); // jeden reservierten Block freigeben
free(b);
return 0;
}16 0 0 16 81
| Funktion | Wirkung |
|---|---|
malloc(bytes) | reserviert, Inhalt unbestimmt |
calloc(n, groesse) | reserviert und setzt alles auf 0 |
realloc(p, neu) | ändert die Größe; kann NULL liefern |
free(p) | gibt frei (free(NULL) ist erlaubt) |
Wichtige Regeln:
- Prüfe, ob
mallocNULLgeliefert hat. - Schreibe
sizeof *zeigerstattsizeof(int): so passt es sich beim Typwechsel an. - Jeder
mallocbraucht genau einfree. - Nach
freeden Zeiger aufNULLsetzen. - Bei
reallocdas Ergebnis erst in einer neuen Variable prüfen (sonst geht bei Fehler der alte Block verloren).
Typische Speicherfehler
| Fehler | Beispiel | Folge |
|---|---|---|
| Speicherleck (memory leak) | malloc ohne free | Programm braucht immer mehr Speicher |
| Doppeltes free | zweimal free(p) | Absturz, Sicherheitslücke |
| Use after free | Zugriff nach free | undefiniertes Verhalten |
| Pufferüberlauf | Schreiben hinter den Block | Datenverlust, Sicherheitslücke |
| Nicht initialisiert | malloc ohne Schreiben, dann lesen | zufällige Werte |
Ein wachsendes Array (Vektor)
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int *daten;
size_t groesse, kapazitaet;
} Vektor;
static int vektor_push(Vektor *v, int wert) {
if (v->groesse == v->kapazitaet) {
size_t neu = v->kapazitaet ? v->kapazitaet * 2 : 4; // Kapazität verdoppeln
int *tmp = realloc(v->daten, neu * sizeof *tmp);
if (!tmp) return 0;
v->daten = tmp;
v->kapazitaet = neu;
}
v->daten[v->groesse++] = wert;
return 1;
}
int main(void) {
Vektor v = {0};
for (int i = 1; i <= 10; i++) if (!vektor_push(&v, i * i)) return 1;
printf("%zu Elemente, Kapazität %zu, letztes %d\n", v.groesse, v.kapazitaet, v.daten[v.groesse - 1]);
free(v.daten);
return 0;
}10 Elemente, Kapazität 16, letztes 100
Strings dynamisch
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *kopie(const char *s) {
size_t n = strlen(s) + 1; // +1 für '\0'
char *k = malloc(n);
if (k) memcpy(k, s, n);
return k;
}
char *verbinde(const char *a, const char *b) {
size_t n = strlen(a) + strlen(b) + 1;
char *r = malloc(n);
if (r) snprintf(r, n, "%s%s", a, b);
return r;
}
int main(void) {
char *k = kopie("Hallo");
char *v = verbinde(k, ", Welt");
printf("%s (%zu)\n", v, strlen(v));
free(k);
free(v);
return 0;
}Hallo, Welt (11)
Gib in der Dokumentation an, wer den Speicher freigeben muss ("Der Aufrufer muss free aufrufen"). Das nennt man Besitzverhältnis (ownership).
Fehler finden mit Werkzeugen
gcc -g -fsanitize=address,undefined -o prog prog.c # AddressSanitizer + UBSan
./prog
valgrind --leak-check=full ./prog # prüft Lecks und ungültige Zugriffe (Linux)Der AddressSanitizer meldet Pufferüberläufe, Use-after-free und Lecks mit Zeilennummer. Benutze ihn beim Entwickeln und in Tests immer.
Aufräumen bei Fehlern: das goto-Muster
#include <stdio.h>
#include <stdlib.h>
int verarbeite(void) {
int status = -1;
int *a = malloc(100 * sizeof *a);
if (!a) goto ende;
int *b = malloc(100 * sizeof *b);
if (!b) goto frei_a;
a[0] = b[0] = 7;
status = a[0] + b[0];
free(b);
frei_a:
free(a);
ende:
return status;
}
int main(void) {
printf("%d\n", verarbeite());
return 0;
}14
So wird in jedem Fehlerfall genau das freigegeben, was schon reserviert war.
Merke
- Stack: automatisch und klein; Heap: von Hand mit
malloc/free mallocprüfen,sizeof *pverwenden, jeden Block genau einmal freigebenreallocin eine Hilfsvariable; Zeiger nachfreeaufNULL- Fehlerbilder: Leck, doppeltes free, use after free, Überlauf
- AddressSanitizer und Valgrind finden Speicherfehler
Aufgabe
Schreibe char *zeile_einlesen(FILE *f), die eine beliebig lange Zeile in einen wachsenden Puffer liest und zurückgibt (der Aufrufer ruft free).