EMZETT.
Login

Kurz: 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.

Teil des Kurses C

Kapitel 11 von 15 im Kurs C. Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

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.

StackHeap
Verwaltungautomatischvon Hand (malloc/free)
Größeklein (oft 1 bis 8 MB)groß (Arbeitsspeicher)
Lebensdauerbis Blockendebis free
Geschwindigkeitsehr schnelllangsamer

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;
}

Ausgabe:

16 0 0
16 81
FunktionWirkung
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:

  1. Prüfe, ob malloc NULL geliefert hat.
  2. Schreibe sizeof *zeiger statt sizeof(int): so passt es sich beim Typwechsel an.
  3. Jeder malloc braucht genau ein free.
  4. Nach free den Zeiger auf NULL setzen.
  5. Bei realloc das Ergebnis erst in einer neuen Variable prüfen (sonst geht bei Fehler der alte Block verloren).

Typische Speicherfehler

FehlerBeispielFolge
Speicherleck (memory leak)malloc ohne freeProgramm braucht immer mehr Speicher
Doppeltes freezweimal free(p)Absturz, Sicherheitslücke
Use after freeZugriff nach freeundefiniertes Verhalten
PufferüberlaufSchreiben hinter den BlockDatenverlust, Sicherheitslücke
Nicht initialisiertmalloc ohne Schreiben, dann lesenzufä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;
}

Ausgabe:

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;
}

Ausgabe:

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;
}

Ausgabe:

14

So wird in jedem Fehlerfall genau das freigegeben, was schon reserviert war.

Merke

  • Stack: automatisch und klein; Heap: von Hand mit malloc/free
  • malloc prüfen, sizeof *p verwenden, jeden Block genau einmal freigeben
  • realloc in eine Hilfsvariable; Zeiger nach free auf NULL
  • Fehlerbilder: Leck, doppeltes free, use after free, Überlauf
  • AddressSanitizer und Valgrind finden Speicherfehler

Übungsaufgabe

Schreibe char *zeile_einlesen(FILE *f), die eine beliebig lange Zeile in einen wachsenden Puffer liest und zurückgibt (der Aufrufer ruft free).

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Strukturen, Enums und Unions

Weiter: Dateien lesen und schreiben

Alle Kapitel: C im Überblick