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,reallocundfopen, bevor du ihn benutzt. - Prüfe Zeigerparameter oben in jeder Funktion, die mit schlechter Eingabe aufgerufen werden könnte.
- Setze Zeiger unmittelbar nach
freeaufNULL. - Schreibe
NULL, nicht0, wann immer ein Zeiger gemeint ist. - Baue mit
-Wall -Wextraund lass, wenn möglich, einen Sanitizer mitlaufen:gcc -fsanitize=address,undefinedfä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.