Menu

NULL-Zeiger in C: Was NULL bedeutet und wie man darauf prüft

NULL ist die Adresse, die ein Zeiger hält, wenn er auf nichts zeigt. Diese Seite behandelt, was NULL wirklich ist, warum das Dereferenzieren abstürzt, wie es sich von 0 und von einem uninitialisierten Zeiger unterscheidet, und die Schutzmuster, die Null-Bugs aus deinem Code halten.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Ein Zeiger hält immer irgendeinen Wert. Wenn er noch kein Objekt hat, auf das er zeigen kann - vor der Zuweisung, nachdem das, worauf er zeigte, freigegeben wurde, oder wenn eine Funktion nichts zurückzugeben hat -, braucht er einen Wert, der unmissverständlich „hier ist nichts" bedeutet. Dieser Wert ist der Nullzeiger, und NULL ist der Name, den du schreibst.

Die Disziplin drumherum ist klein und macht sich ständig bezahlt: Setze Zeiger auf NULL, wenn sie kein Ziel haben, prüfe vor dem Dereferenzieren auf NULL, und liefere NULL, wenn du kein Ergebnis hast.

Was NULL tatsächlich ist

NULL ist ein Makro, definiert in <stddef.h> und ebenfalls von <stdio.h>, <stdlib.h>, <string.h> und anderen hereingeholt. Es expandiert zu einer Nullzeigerkonstante - typischerweise ((void*)0) oder einer schlichten 0.

Der C-Standard garantiert die Eigenschaft, auf die es ankommt: Ein Nullzeiger ist ungleich einem Zeiger auf jedes tatsächliche Objekt oder jede Funktion. Zwei Nullzeiger desselben Typs sind immer gleich.

Die meisten Systeme geben für den Nullzeiger (nil) oder 0x0 aus - Adresse null. Das ist eine verbreitete Implementierung, keine Regel; der Standard verspricht nie, dass die Bits null sind. Was er verspricht, ist der Vergleich, schreibe also p == NULL und nie etwas, das von der Darstellung abhängt.

NULL zu dereferenzieren stürzt ab

Der Grund für jede Nullprüfung, die es gibt:

int *p = NULL;
printf("%d\n", *p);   // undefiniertes Verhalten - fast sicher ein Absturz

Unter Windows, macOS und Linux bleibt die erste Seite des Adressraums absichtlich unabgebildet, die CPU fängt den Zugriff also ab und das Betriebssystem beendet das Programm. Du siehst:

Segmentation fault (core dumped)          # Linux
zsh: segmentation fault  ./program        # macOS

Dieser sofortige Absturz ist eine Stärke. Er verwandelt einen Logikfehler in ein lautes Scheitern genau in der Zeile, die ihn verursacht hat, statt in eine stille Korruption, die du drei Wochen später findest. Siehe Segmentierungsfehler, wie man den Absturz liest und die Zeile findet.

Deshalb ist „Nullzeiger" in C keine Ausnahme, die man abfängt. Es gibt keinen Mechanismus zur Erholung; es gibt nur das Prüfen vorher.

Prüfe, was fehlschlagen kann

Funktionen, die scheitern können, signalisieren es mit NULL als Rückgabewert. Zu prüfen ist keine optionale Höflichkeit - die Alternative ist der Absturz.

Die Standardbibliothek ist voll davon: malloc, calloc, realloc, fopen, strchr, strstr, getenv, bsearch. Jede davon antwortet auf „kein Ergebnis" mit NULL. Siehe dynamischer Speicher für die ganze Geschichte der Belegung.

Zwei Idiome für die Prüfung, beide verbreitet:

if (p == NULL) { /* behandeln */ }   // ausdrücklich - der Klarheit halber bevorzugt
if (!p)        { /* behandeln */ }   // knapp - verlässt sich darauf, dass NULL falsch ist

Ein Nullzeiger gilt in einer Bedingung als falsch und ein gültiger Zeiger als wahr, beides funktioniert also. Die ausdrückliche Form liest sich besser, wenn ein Fremder deinen Code nach Bugs durchsieht.

NULL gegen 0 gegen uninitialisiert

Drei verschiedene Situationen, die Anfänger vermengen:

int *a = NULL;     "zeigt auf nichts" - prüfbar, sicher zu testen
int *b;            uninitialisiert - hält Müll, NICHT prüfbar
int *c = &x;       zeigt auf ein echtes Objekt

Das Gefährliche ist b. Seine Bytes sind das, was in diesem Stackplatz übrig war, was null sein kann (und in Ordnung aussieht) oder eine plausibel wirkende Adresse aus einem früheren Aufruf. Keine Prüfung kann es von einem gültigen Zeiger unterscheiden, und das Verhalten ändert sich zwischen Debug- und Release-Builds.

Initialisiere jeden Zeiger. Wenn du noch keine Adresse hast, ist NULL die Adresse. Kompiliere mit -Wall -Wextra, und der Compiler beanstandet viele uninitialisierte Zugriffe für dich.

Zur 0: In einem Zeigerzusammenhang ist die Ganzzahlkonstante 0 die Nullzeigerkonstante, p = 0; ist also gültiges C. Bevorzuge trotzdem NULL. Es zeigt die Absicht an, und es zählt bei Aufrufen mit variabler Argumentzahl, wo der Compiler nicht für dich umwandeln kann:

execl("/bin/ls", "ls", 0);              // riskant - kann ein int übergeben, wo ein Zeiger erwartet wird
execl("/bin/ls", "ls", (char *)NULL);   // korrekt

C23 fügt nullptr hinzu, ein Schlüsselwort mit eigenem Typ, das diese Klasse von Mehrdeutigkeit ganz vermeidet; NULL bleibt überall korrekt und portabel.

Defensive Muster

Prüfe oben in einer Funktion, die Zeiger entgegennimmt.

Setze nach dem Freigeben auf NULL. free ändert deinen Zeiger nicht - es gibt nur den Speicher frei. Der zurückbleibende veraltete Zeiger ist ein baumelnder Zeiger, und ihn zu benutzen ist undefiniertes Verhalten, das oft nicht sofort abstürzt.

Zwei Tatsachen in diesem Beispiel sollte man sich merken. free(NULL) ist so definiert, dass es nichts tut, Aufräumcode muss es also nie absichern. Und p = NULL nach free zu setzen verwandelt ein Use-after-free - das still Daten beschädigen kann - in einen sofortigen, debugbaren Absturz.

Liefere NULL für „kein Ergebnis" und schreib es hin.

// liefert einen Zeiger auf das passende Element oder NULL, wenn es keines gibt
int *find(int *arr, int n, int target);

Dokumentiere es im Kommentar über der Funktion. Ein Aufrufer, der weiß, dass ein NULL möglich ist, schreibt die Prüfung; einer, der es nicht weiß, tut es nicht.

Eine kurze Checkliste

  • Initialisiere jeden Zeiger, mit einer echten Adresse oder mit NULL.
  • Prüfe den Rückgabewert von malloc, calloc, realloc und fopen, bevor du ihn benutzt.
  • Prüfe Zeigerparameter oben in jeder Funktion, die mit schlechter Eingabe aufgerufen werden könnte.
  • Setze Zeiger unmittelbar nach free auf NULL.
  • Schreibe NULL, nicht 0, wann immer ein Zeiger gemeint ist.
  • Baue mit -Wall -Wextra und lass, wenn möglich, einen Sanitizer mitlaufen: gcc -fsanitize=address,undefined fängt Null-Dereferenzierungen mit einem präzisen Bericht ab.

Häufig gestellte Fragen

Was ist ein NULL-Zeiger in C?

Ein Zeiger, der die Nullzeigerkonstante hält - einen Wert, der garantiert ungleich der Adresse jedes echten Objekts ist. Es ist die übliche Art zu sagen „dieser Zeiger zeigt noch auf nichts", und NULL ist das Makro dafür, definiert in <stddef.h> und mehreren weiteren Headern.

Was passiert, wenn man in C einen NULL-Zeiger dereferenziert?

Es ist undefiniertes Verhalten. In der Praxis stürzt es auf Desktop- und Serversystemen sofort mit einem Segmentierungsfehler ab, weil die Adresse null absichtlich unabgebildet bleibt. Auf Embedded-Systemen ohne Speicherschutz kann es stattdessen still etwas lesen oder beschädigen, was weit schlimmer ist.

Ist NULL dasselbe wie 0 in C?

In Zeigerzusammenhängen ja: Die Ganzzahlkonstante 0 ist die Nullzeigerkonstante, p = 0; und p == NULL funktionieren also beide. Benutze aber NULL für Zeiger und 0 für Zahlen - das sagt dem Leser, was du gemeint hast, und es hält Aufrufe mit variabler Argumentzahl sicher, wo eine nackte 0 als int statt als Zeiger übergeben werden kann.

Was ist der Unterschied zwischen einem NULL-Zeiger und einem uninitialisierten Zeiger?

Ein NULL-Zeiger zeigt definitiv auf nichts, und du kannst darauf prüfen. Ein uninitialisierter Zeiger hält die Bytes, die zufällig in diesem Speicher lagen - womöglich eine gültig aussehende Adresse -, es gibt also keinen Test, der ihn abfangen kann. Initialisiere Zeiger immer mit NULL, wenn du nichts Besseres hast.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S