Was bedeutet es, in C oder C ++ eine Nullprüfung durchzuführen?

21

Ich habe C ++ gelernt und es fällt mir schwer, null zu verstehen. Insbesondere in den von mir gelesenen Tutorials wird erwähnt, dass ein "Null-Check" durchgeführt wird. Ich bin mir jedoch nicht sicher, was dies bedeutet oder warum dies erforderlich ist.

  • Was genau ist null?
  • Was bedeutet es, auf Null zu prüfen?
  • Muss ich immer auf null prüfen?

Irgendwelche Codebeispiele wären sehr dankbar.

kdi
quelle
Wann: programmers.stackexchange.com/questions/186036/...
Ciro Santilli新疆改造中心法轮功六四事件
Ich würde raten, einige bessere Tutorials zu bekommen, wenn alle, die Sie lesen, über Nullprüfungen sprechen, ohne sie jemals zu erklären und Beispielcode bereitzustellen ...
underscore_d

Antworten:

26

In C und C ++ sind Zeiger von Natur aus unsicher. Wenn Sie also einen Zeiger dereferenzieren, liegt es in Ihrer eigenen Verantwortung, sicherzustellen, dass er irgendwo gültig ist. Dies ist ein Teil dessen, worum es bei der "manuellen Speicherverwaltung" geht (im Gegensatz zu den in Sprachen wie Java, PHP oder der .NET-Laufzeit implementierten automatischen Speicherverwaltungsschemata, mit denen Sie keine ungültigen Referenzen ohne erheblichen Aufwand erstellen können).

Eine häufige Lösung, die viele Fehler abfängt, besteht darin, alle Zeiger zu setzen, die nicht auf irgendetwas verweisen NULL(oder in korrektem C ++ 0), und dies zu überprüfen, bevor auf den Zeiger zugegriffen wird. Insbesondere ist es üblich, alle Zeiger auf NULL zu initialisieren (es sei denn, Sie haben bereits etwas, auf das Sie beim Deklarieren verweisen sollen), und sie auf NULL zu setzen, wenn Sie deleteoder free()sie dies tun (es sei denn, sie verlieren unmittelbar danach ihren Gültigkeitsbereich). Beispiel (in C, aber auch in C ++):

void fill_foo(int* foo) {
    *foo = 23; // this will crash and burn if foo is NULL
}

Eine bessere Version:

void fill_foo(int* foo) {
    if (!foo) { // this is the NULL check
        printf("This is wrong\n");
        return;
    }
    *foo = 23;
}

Ohne die Null-Prüfung führt die Übergabe eines NULL-Zeigers an diese Funktion zu einem Segfault, und Sie können nichts tun - das Betriebssystem beendet einfach Ihren Prozess und führt möglicherweise einen Core-Dump durch oder öffnet ein Dialogfeld mit Absturzberichten. Mit der Null-Prüfung können Sie die Fehler ordnungsgemäß behandeln und ordnungsgemäß beheben. Beheben Sie das Problem selbst, brechen Sie den aktuellen Vorgang ab, schreiben Sie einen Protokolleintrag, und benachrichtigen Sie den Benutzer, was auch immer angemessen ist.

tdammers
quelle
3
@MrLister was meinst du, Nullprüfungen funktionieren in C ++ nicht? Sie müssen nur den Zeiger auf null initialisieren, wenn Sie ihn deklarieren.
TZHX
1
Ich meine, Sie müssen daran denken, den Zeiger auf NULL zu setzen, sonst funktioniert es nicht. Und wenn Sie sich erinnern, mit anderen Worten, wenn Sie wissen, dass der Zeiger NULL ist, müssen Sie fill_foo sowieso nicht aufrufen. fill_foo prüft, ob der Zeiger einen Wert hat, nicht, ob der Zeiger einen gültigen Wert hat. In C ++ wird nicht garantiert, dass Zeiger NULL sind oder einen gültigen Wert haben.
Mr Lister
4
Ein assert () wäre hier eine bessere Lösung. Es hat keinen Sinn zu versuchen, "sicher zu sein". Wenn NULL übergeben wurde, ist dies offensichtlich falsch. Warum also nicht einfach explizit abstürzen, um den Programmierer vollständig darauf aufmerksam zu machen? (Und in der Produktion spielt es keine Rolle, weil Sie bewiesen haben, dass niemand fill_foo () mit NULL aufrufen wird, richtig? Wirklich, es ist nicht so schwer.)
Ambroz Bizjak
7
Vergessen Sie nicht zu erwähnen, dass eine noch bessere Version dieser Funktion Referenzen anstelle von Zeigern verwenden sollte, wodurch die NULL-Prüfung überholt wird.
Doc Brown
4
Dies ist nicht die Aufgabe der manuellen Speicherverwaltung, und ein verwaltetes Programm wird ebenfalls explodieren (oder zumindest eine Ausnahme auslösen, genau wie ein natives Programm in den meisten Sprachen), wenn Sie versuchen, eine Nullreferenz dereferenzieren.
Mason Wheeler
7

Die anderen Antworten deckten so ziemlich Ihre genaue Frage ab. Es wird eine Nullprüfung durchgeführt, um sicherzustellen, dass der Zeiger, den Sie erhalten haben, tatsächlich auf eine gültige Instanz eines Typs (Objekte, Grundelemente usw.) verweist.

Ich werde hier jedoch meine eigenen Ratschläge hinzufügen. Vermeiden Sie Nullprüfungen. :) Null überprüft (und andere Formen der defensiven Programmierung) den Code auf Unordnung und macht ihn tatsächlich fehleranfälliger als andere Fehlerbehandlungstechniken.

Meine bevorzugte Technik bei Objektzeigern ist die Verwendung des Null-Objektmusters . Das bedeutet, dass ein (Zeiger - oder noch besser, ein Verweis auf ein) leeres Array oder eine leere Liste anstelle von null oder eine leere Zeichenfolge ("") anstelle von null oder sogar die Zeichenfolge "0" (oder etwas, das "nichts" entspricht) zurückgegeben wird msgstr "in dem Kontext), in dem erwartet wird, dass es in eine ganze Zahl zerlegt wird.

Als Bonus haben Sie vielleicht etwas Unbekanntes über den Nullzeiger erfahren, der 1965 von CAR Hoare (erstmals offiziell) für die Sprache Algol W implementiert wurde.

Ich nenne es meinen Milliarden-Dollar-Fehler. Es war die Erfindung der Nullreferenz im Jahr 1965. Damals entwarf ich das erste umfassende Typensystem für Referenzen in einer objektorientierten Sprache (ALGOL W). Mein Ziel war es, sicherzustellen, dass jede Verwendung von Referenzen absolut sicher ist, wobei die Überprüfung automatisch vom Compiler durchgeführt wird. Aber ich konnte der Versuchung nicht widerstehen, eine Nullreferenz einzufügen, einfach weil es so einfach zu implementieren war. Dies hat zu unzähligen Fehlern, Sicherheitslücken und Systemabstürzen geführt, die in den letzten vierzig Jahren wahrscheinlich eine Milliarde Dollar an Schmerzen und Schäden verursacht haben.

Yam Marcovic
quelle
6
Null-Objekt ist noch schlimmer als nur einen Nullzeiger zu haben. Wenn ein Algorithmus X Daten Y benötigt, die Sie nicht haben, dann ist das ein Fehler in Ihrem Programm , den Sie einfach verstecken, indem Sie so tun, als ob Sie dies tun.
DeadMG
Es hängt vom Kontext ab, und in beiden Fällen schlägt das Testen auf "Datenpräsenz" das Testen auf Null in meinem Buch. Wenn ein Algorithmus beispielsweise an einer Liste arbeitet und die Liste leer ist, hat der Algorithmus meiner Erfahrung nach einfach nichts zu tun, und dies wird nur durch die Verwendung von Standardsteueranweisungen wie for / foreach erreicht.
Yam Marcovic
Wenn der Algorithmus nichts zu tun hat, warum rufen Sie ihn dann überhaupt auf? Und der Grund, warum Sie es vielleicht als erstes nennen wollten, ist, dass es etwas Wichtiges tut .
DeadMG
@DeadMG Da es in Programmen um Eingaben geht, kann die Eingabe in der realen Welt im Gegensatz zu Hausaufgaben irrelevant sein (z. B. leer). Code wird immer noch so oder so aufgerufen. Sie haben zwei Möglichkeiten: Entweder Sie überprüfen die Relevanz (oder die Leere) oder Sie entwerfen Ihre Algorithmen so, dass sie gut lesen und funktionieren, ohne explizit die Relevanz mithilfe von bedingten Anweisungen zu überprüfen.
Yam Marcovic
Ich bin hierher gekommen, um fast den gleichen Kommentar zu machen, also habe ich stattdessen meine Stimme abgegeben. Ich möchte jedoch auch hinzufügen, dass dies für ein größeres Problem von Zombieobjekten repräsentativ ist - immer dann, wenn Sie Objekte mit mehrstufiger Initialisierung (oder Zerstörung) haben, die nicht vollständig lebendig, aber nicht ganz tot sind. Wenn in Sprachen "sicherer" Code ohne deterministische Finalisierung angezeigt wird, bei der jede Funktion überprüft wird, ob das Objekt entsorgt wurde, liegt dieses allgemeine Problem darin, den Kopf zu heben. Sie sollten niemals if-null verwenden, sondern mit Zuständen arbeiten, die die Objekte enthalten, die sie für ihre Lebensdauer benötigen.
ex0du5
4

Der Nullzeigerwert repräsentiert ein wohldefiniertes "Nirgendwo". Es handelt sich um einen ungültigen Zeigerwert, der garantiert mit keinem anderen Zeigerwert verglichen werden kann. Der Versuch, einen Nullzeiger zu dereferenzieren, führt zu undefiniertem Verhalten und führt normalerweise zu einem Laufzeitfehler. Stellen Sie daher sicher, dass ein Zeiger nicht NULL ist, bevor Sie ihn dereferenzieren. Eine Reihe von C- und C ++ - Bibliotheksfunktionen geben einen Nullzeiger zurück, um einen Fehlerzustand anzuzeigen. Beispielsweise gibt die Bibliotheksfunktion malloceinen Nullzeigerwert zurück, wenn sie die Anzahl der angeforderten Bytes nicht zuordnen kann, und der Versuch, über diesen Zeiger auf den Speicher zuzugreifen, führt (normalerweise) zu einem Laufzeitfehler:

int *p = malloc(sizeof *p * N);
p[0] = ...; // this will (usually) blow up if malloc returned NULL

Wir müssen also sicherstellen, dass der mallocAufruf erfolgreich war, indem wir den Wert von pgegen NULL prüfen :

int *p = malloc(sizeof *p * N);
if (p != NULL) // or just if (p)
  p[0] = ...;

Bleib eine Minute bei deinen Socken, das wird ein bisschen holprig.

Es gibt einen Nullzeiger - Wert und eine Null - Zeiger - Konstante , und die beiden sind nicht notwendigerweise gleich. Der Null - Zeiger - Wert ist , was die zugrunde liegende Architektur Nutzungen schätzen „nirgendwo“ darzustellen. Dieser Wert kann 0x00000000 oder 0xFFFFFFFF oder 0xDEADBEEF oder etwas völlig anderes sein. Gehen Sie nicht davon , dass der Null - Zeiger - Wert ist immer 0.

Der Nullzeiger Konstante OTOH ist immer ein ganzzahliger Ausdruck mit dem Wert 0. Für Ihren Quellcode steht 0 (oder ein ganzzahliger Ausdruck, der mit 0 bewertet wird) für einen Nullzeiger. Sowohl C als auch C ++ definieren das NULL-Makro als Nullzeiger-Konstante. Wenn Ihr Code kompiliert wird, wird die Nullzeiger- Konstante durch den entsprechenden Nullzeiger- Wert im generierten Maschinencode ersetzt.

Beachten Sie außerdem, dass NULL nur einer von vielen möglichen ungültigen Zeigerwerten ist. Wenn Sie eine Auto-Pointer-Variable deklarieren, ohne sie explizit zu initialisieren, wie z

int *p;

Der ursprünglich in der Variablen gespeicherte Wert ist unbestimmt und entspricht möglicherweise nicht einer gültigen oder zugänglichen Speicheradresse. Leider gibt es keine (portable) Möglichkeit, festzustellen, ob ein Zeigerwert ungleich NULL gültig ist oder nicht, bevor Sie versuchen, ihn zu verwenden. Wenn Sie es also mit Zeigern zu tun haben, ist es normalerweise eine gute Idee, sie explizit auf NULL zu initialisieren, wenn Sie sie deklarieren, und sie auf NULL zu setzen, wenn sie nicht aktiv auf irgendetwas zeigen.

Beachten Sie, dass dies in C eher ein Problem ist als in C ++. idiomatisches C ++ sollte nicht allzu oft Zeiger verwenden.

John Bode
quelle
3

Es gibt ein paar Methoden, die alle im Wesentlichen dasselbe tun.

int * foo = NULL; // manchmal auf 0x00 oder 0 oder 0L anstelle von NULL gesetzt

null check (prüfe, ob der Zeiger null ist), Version A

if (foo == NULL)

Null-Check, Version B

if (! foo) // da NULL als 0 definiert ist, gibt! foo einen Wert von einem Nullzeiger zurück

Null-Check, Version C

if (foo == 0)

Von den dreien bevorzuge ich die erste Prüfung, da sie zukünftigen Entwicklern explizit mitteilt, worauf Sie zu prüfen versuchten, UND es verdeutlicht, dass Sie erwartet haben, dass foo ein Zeiger ist.


quelle
2

Das tust du nicht. Der einzige Grund für die Verwendung eines Zeigers in C ++ besteht darin, dass Sie explizit das Vorhandensein von Nullzeigern wünschen. Andernfalls können Sie eine Referenz verwenden, die semantisch einfacher zu verwenden ist und garantiert, dass sie nicht null ist.

DeadMG
quelle
1
@James: 'Neu' im Kernel-Modus?
Nemanja Trifunovic
1
@James: Eine Implementierung von C ++, die die Fähigkeiten darstellt, über die eine bedeutende Mehrheit der C ++ - Codierer verfügt. Dies beinhaltet alle C ++ 03-Sprachfunktionen (außer export) und alle C ++ 03-Bibliotheksfunktionen sowie TR1 und einen guten Teil von C ++ 11.
DeadMG
5
Ich tue Wunsch Menschen würden nicht sagen , dass „Referenzen nicht-null garantieren.“ Sie tun es nicht. Es ist so einfach, eine Nullreferenz wie einen Nullzeiger zu generieren, und sie verbreiten sich auf dieselbe Weise.
mjfgates
2
@Stargazer: Die Frage ist zu 100% überflüssig, wenn Sie die Tools nur so verwenden, wie es die Sprachdesigner und bewährten Verfahren empfehlen.
DeadMG
2
@DeadMG, es ist egal, ob es redundant ist. Du hast die Frage nicht beantwortet . Ich sage es noch einmal: -1.
Riwalk
-1

Wenn Sie den NULL-Wert nicht überprüfen, insbesondere wenn dies ein Zeiger auf eine Struktur ist, ist möglicherweise eine Sicherheitslücke aufgetreten - NULL-Zeiger-Dereferenzierung. Eine NULL-Zeiger-Dereferenzierung kann zu einigen anderen schwerwiegenden Sicherheitslücken wie Pufferüberlauf, Race Condition ... führen, durch die Angreifer die Kontrolle über Ihren Computer erlangen können.

Viele Softwareanbieter wie Microsoft, Oracle, Adobe, Apple ... veröffentlichen Software-Patches, um diese Sicherheitslücken zu schließen. Ich denke, Sie sollten NULL-Wert jedes Zeigers überprüfen :)

oDisPo
quelle