Überprüfung auf NULL-Zeiger in C / C ++ [geschlossen]


153

In einer kürzlich durchgeführten Codeüberprüfung versucht ein Mitwirkender zu erzwingen, dass alle NULLÜberprüfungen von Zeigern auf folgende Weise durchgeführt werden:

int * some_ptr;
// ...
if (some_ptr == NULL)
{
    // Handle null-pointer error
}
else
{
    // Proceed
}

anstatt

int * some_ptr;
// ...
if (some_ptr)
{
    // Proceed
}
else
{
    // Handle null-pointer error
}

Ich stimme zu, dass sein Weg etwas klarer ist in dem Sinne, dass er explizit "Stellen Sie sicher, dass dieser Zeiger nicht NULL ist" sagt, aber ich würde dem entgegenwirken, indem ich sage, dass jeder, der an diesem Code arbeitet, verstehen würde, dass die Verwendung einer Zeigervariablen in einem ifAnweisung prüft implizit auf NULL. Ich bin auch der Meinung, dass die zweite Methode eine geringere Chance hat, einen Fehler der Art einzuführen:

if (some_ptr = NULL)

Das ist nur ein absoluter Schmerz zu finden und zu debuggen.

Welchen Weg bevorzugen Sie und warum?


32
Ein einheitlicher Stil ist manchmal wichtiger als der Stil, den Sie auswählen.
Mark Ransom

15
@Mark: Konsistenz ist immer der wichtigste Faktor Ihres Stils.
sbi

13
"Ich bin auch der Meinung, dass die zweite Methode eine geringere Wahrscheinlichkeit hat, einen Fehler des Typs einzuführen: if (some_ptr = NULL)" Aus diesem Grund können einige Leute if (NULL == some_ptr) nicht NULL zuweisen, so dass Sie erhalten würden ein Kompilierungsfehler.
user122302

14
Die meisten Compiler warnen heutzutage vor der Zuweisung in Bedingungen - leider ignorieren zu viele Entwickler die Warnungen des Compilers.
Mike Ellery

10
Ich stimme der obigen Aussage nicht zu, dass es auf Konsistenz ankommt. Dies hat nichts mit Konsistenz zu tun; sowieso nicht die gute Sorte. Dies ist der Bereich, in dem wir Programmierer ihren eigenen Stil ausdrücken lassen sollten. Wenn es eine Person gibt, die beide Formen nicht sofort versteht, sollte sie sofort aufhören, Code zu lesen, und über eine berufliche Veränderung nachdenken. Ich sage mehr als das: Wenn es eine Kosmetikkonsistenz gibt, die Sie nicht automatisch mit einem Skript erzwingen können, entfernen Sie es. Die Kosten der menschlichen moralischen Trockenlegung ist WAY wichtiger als die dummen Entscheidungen in einer Sitzung machen.
Wilhelmtell

Antworten:


203

Nach meiner Erfahrung werden Tests der Form if (ptr)oder if (!ptr)bevorzugt. Sie hängen nicht von der Definition des Symbols ab NULL. Sie bieten keine Möglichkeit für die versehentliche Zuordnung. Und sie sind klar und prägnant.

Bearbeiten: Wie SoapBox in einem Kommentar hervorhebt, sind sie mit C ++ - Klassen kompatibel, z. B. auto_ptrObjekten, die als Zeiger fungieren und eine Konvertierung bereitstellen bool, um genau diese Redewendung zu aktivieren. Für diese Objekte NULLmüsste ein expliziter Vergleich mit eine Konvertierung in einen Zeiger aufrufen, der andere semantische Nebenwirkungen haben oder teurer sein kann als die einfache Existenzprüfung, die die boolKonvertierung impliziert.

Ich bevorzuge Code, der sagt, was er ohne nicht benötigten Text bedeutet. if (ptr != NULL)hat die gleiche Bedeutung wie if (ptr)auf Kosten der redundanten Spezifität. Die nächste logische Sache ist zu schreiben if ((ptr != NULL) == TRUE)und so liegt der Wahnsinn. Die C-Sprache ist klar, dass ein Boolescher Wert if, der von whileoder dergleichen getestet wurde oder eine spezifische Bedeutung des Werts ungleich Null hat, wahr und Null falsch ist. Redundanz macht es nicht klarer.


23
Darüber hinaus sind sie mit Zeiger-Wrapper-Klassen (shared_ptr, auto_ptr, scoped_tr usw.) kompatibel, die normalerweise den Operator bool (oder safe_bool) überschreiben.
SoapBox

2
Ich stimme dies als "Antwort" ab, einfach aufgrund des Verweises auf auto_ptr, der zur Diskussion hinzugefügt wird. Nach dem Schreiben der Frage ist klar, dass dies tatsächlich subjektiver ist als alles andere und wahrscheinlich nicht für eine Stapelüberlauf- "Antwort" geeignet ist, da jede dieser Fragen je nach den Umständen "korrekt" sein kann.
Bryan Marble

2
@Bryan, ich respektiere das und wenn eine "bessere" Antwort kommt, können Sie das Häkchen nach Bedarf verschieben. Die Subjektivität ist ja subjektiv. Aber es ist zumindest eine subjektive Diskussion, bei der es objektive Probleme gibt, die nicht immer offensichtlich sind, wenn die Frage gestellt wird. Die Linie ist verschwommen. Und subjektiv ... ;-)
RBerteig

1
Eine wichtige Sache zu beachten: Bis vor kurzem war ich dafür, "if (ptr)" anstelle von "if (ptr! = NULL)" oder "if (ptr! = Nullptr)" zu schreiben, da es prägnanter ist der Code besser lesbar. Aber ich habe gerade herausgefunden, dass der Vergleich (bzw.) Warnungen / Fehler für den Vergleich mit NULL / nullptr bei neueren Compilern auslöst. Wenn Sie also einen Zeiger überprüfen möchten, tun Sie besser "if (ptr! = NULL)" anstelle von "if (ptr)". Wenn Sie ptr fälschlicherweise als int deklarieren, löst die erste Prüfung eine Warnung / einen Fehler aus, während die letztere ohne Warnung kompiliert wird.
Arnaud

1
@ David 天宇 Wong Nein, das war nicht gemeint. Das Beispiel auf dieser Seite ist die zweizeilige Sequenz, int y = *x; if (!x) {...}in der der Optimierer begründen darf, dass, da *xundefiniert wäre, wenn xNULL ist, es nicht NULL sein !xdarf und daher FALSE sein muss. Der Ausdruck !ptrist kein undefiniertes Verhalten. Wenn im Beispiel der Test vor der Verwendung von durchgeführt *xwürde, wäre der Optimierer falsch, den Test zu eliminieren.
RBerteig


25

Ich beginne damit: Konsistenz ist König, die Entscheidung ist weniger wichtig als die Konsistenz in Ihrer Codebasis.

In C ++

NULL ist in C ++ als 0 oder 0L definiert.

Wenn Sie die C ++ - Programmiersprache gelesen haben, schlägt Bjarne Stroustrup vor, 0das NULLMakro bei der Zuweisung explizit zu vermeiden. Ich bin mir nicht sicher, ob er dasselbe mit Vergleichen getan hat. Es ist eine Weile her, seit ich das Buch gelesen habe. Ich glaube, er hat es gerade getan if(some_ptr)ohne expliziten Vergleich, aber ich bin mir nicht sicher.

Der Grund dafür ist, dass das NULLMakro täuscht (wie fast alle Makros), es ist tatsächlich 0wörtlich und kein eindeutiger Typ, wie der Name vermuten lässt. Das Vermeiden von Makros ist eine der allgemeinen Richtlinien in C ++. Auf der anderen Seite 0sieht es aus wie eine Ganzzahl und ist es nicht, wenn es mit Zeigern verglichen oder diesen zugewiesen wird. Persönlich könnte ich in beide Richtungen gehen, aber normalerweise überspringe ich den expliziten Vergleich (obwohl einige Leute dies nicht mögen, was wahrscheinlich der Grund ist, warum Sie einen Mitwirkenden haben, der sowieso eine Änderung vorschlägt).

Unabhängig von persönlichen Gefühlen ist dies größtenteils eine Wahl des geringsten Übels, da es keine richtige Methode gibt.

Dies ist klar und eine gängige Redewendung, und ich bevorzuge es. Es besteht keine Möglichkeit, dass während des Vergleichs versehentlich ein Wert zugewiesen wird, und es lautet deutlich:

if(some_ptr){;}

Dies ist klar, wenn Sie wissen, dass some_ptres sich um einen Zeigertyp handelt, aber es kann auch wie ein ganzzahliger Vergleich aussehen:

if(some_ptr != 0){;}

Dies ist klar, in den meisten Fällen macht es Sinn ... Aber es ist eine undichte Abstraktion, NULList tatsächlich 0wörtlich und könnte leicht missbraucht werden:

if(some_ptr != NULL){;}

C ++ 0x hat nullptr, was jetzt die bevorzugte Methode ist, da es explizit und genau ist. Seien Sie nur vorsichtig bei versehentlicher Zuweisung:

if(some_ptr != nullptr){;}

Bis Sie in der Lage sind, auf C ++ 0x zu migrieren, ist es meiner Meinung nach Zeitverschwendung, sich Gedanken darüber zu machen, welche dieser Methoden Sie verwenden. Sie sind alle unzureichend, weshalb nullptr erfunden wurde (zusammen mit allgemeinen Programmierproblemen, die zu einer perfekten Weiterleitung führten .) Das Wichtigste ist die Aufrechterhaltung der Konsistenz.

In C.

C ist ein anderes Tier.

In C kann NULL als 0 oder als ((void *) 0) definiert werden, C99 ermöglicht die Implementierung definierter Nullzeigerkonstanten. Es kommt also tatsächlich auf die Definition von NULL durch die Implementierung an, und Sie müssen sie in Ihrer Standardbibliothek überprüfen.

Makros sind sehr verbreitet und werden im Allgemeinen häufig verwendet, um Mängel bei der allgemeinen Programmierunterstützung in der Sprache und anderen Dingen auszugleichen. Die Sprache ist viel einfacher und das Vertrauen in den Vorprozessor häufiger.

Aus dieser Perspektive würde ich wahrscheinlich empfehlen, die NULLMakrodefinition in C zu verwenden.


tl; dr und obwohl Sie 0in C ++ Recht haben , hat es in C : (void *)0. 0ist NULLin C ++ vorzuziehen , da Typfehler eine PITA sein können.
Matt Joiner

@ Matt: Ist aber NULLtrotzdem Null.
GManNickG

@ GMan: Nicht auf meinem System: #define NULL ((void *)0)vonlinux/stddef.h
Matt Joiner

@Matt: In C ++. Sie sagten, 0 ist vorzuziehen, NULLmuss aber Null sein.
GManNickG

Dies ist ein guter Punkt, ich habe es versäumt, ihn zu erwähnen, und ich verbessere meine Antwort, indem ich ihn vorschlage. In ANSI C kann NULL als ((void *) 0) definiert sein, C ++ definiert NULL als 0. Ich habe den Standard dafür nicht direkt durchforstet, aber ich verstehe, dass NULL in C ++ 0 oder 0L sein kann.
M2tM

19

Ich benutze if (ptr), aber das ist es absolut nicht wert, darüber zu streiten.

Ich mag meinen Weg, weil er prägnant ist, obwohl andere sagen, == NULLdass er leichter zu lesen und expliziter ist. Ich sehe, woher sie kommen, ich bin nur anderer Meinung, dass das zusätzliche Zeug es einfacher macht. (Ich hasse das Makro, also bin ich voreingenommen.) Bis zu dir.

Ich bin mit Ihrem Argument nicht einverstanden. Wenn Sie keine Warnungen für Zuweisungen in einer Bedingung erhalten, müssen Sie Ihre Warnstufen erhöhen. So einfach ist das. (Und aus Liebe zu allem, was gut ist, schalten Sie sie nicht um.)

Beachten Sie, dass wir in C ++ 0x etwas tun können if (ptr == nullptr), was für mich besser zu lesen ist. (Wieder hasse ich das Makro. Aber es nullptrist nett.) Ich mache es if (ptr)trotzdem, nur weil es das ist, was ich gewohnt bin.


Ich hoffe, zusätzliche 5 Jahre Erfahrung haben Ihre Meinung geändert :) Wenn C ++ / C einen Operator wie if_exist () hätte, wäre dies sinnvoll.
Magulla

10

Ehrlich gesagt verstehe ich nicht, warum es wichtig ist. Entweder ist man ganz klar und jeder, der mäßig Erfahrung mit C oder C ++ hat, sollte beides verstehen. Ein Kommentar:

Wenn Sie den Fehler erkennen und die Funktion nicht weiter ausführen möchten (dh Sie werden sofort eine Ausnahme auslösen oder einen Fehlercode zurückgeben), sollten Sie daraus eine Schutzklausel machen:

int f(void* p)
{
    if (!p) { return -1; }

    // p is not null
    return 0;
}

Auf diese Weise vermeiden Sie "Pfeilcode".


jemand zeigte hier kukuruku.co/hub/programming/i-do-not-know-c das if(!p)ist undefiniertes Verhalten. Es könnte funktionieren oder nicht.
David 天宇 Wong

@David 天宇 Wong, wenn Sie unter diesem Link über Beispiel 2 sprechen, if(!p)ist dies nicht das undefinierte Verhalten, sondern die Zeile davor. Und if(p==NULL)hätte genau das gleiche Problem.
Mark Ransom

8

Persönlich habe ich immer verwendet, if (ptr == NULL)weil es meine Absicht explizit macht, aber an diesem Punkt ist es nur eine Gewohnheit.

Die Verwendung =anstelle von ==wird von jedem kompetenten Compiler mit den richtigen Warneinstellungen abgefangen.

Der wichtige Punkt ist, einen einheitlichen Stil für Ihre Gruppe auszuwählen und sich daran zu halten. Egal in welche Richtung Sie gehen, Sie werden sich irgendwann daran gewöhnen, und der Verlust an Reibung bei der Arbeit mit dem Code anderer Leute ist willkommen.


7

Nur noch ein Punkt zugunsten der foo == NULLPraxis: Wenn fooes sich beispielsweise um ein int *oder ein handelt bool *, kann die if (foo)Prüfung von einem Leser versehentlich so interpretiert werden, dass sie den Wert des Pointees testet, dh als if (*foo). Der NULLVergleich hier erinnert daran, dass es sich um einen Zeiger handelt.

Aber ich nehme an, eine gute Namenskonvention macht dieses Argument umstritten.


4

Eigentlich benutze ich beide Varianten.

Es gibt Situationen, in denen Sie zuerst die Gültigkeit eines Zeigers überprüfen und wenn er NULL ist, kehren Sie einfach zu einer Funktion zurück oder verlassen sie. (Ich weiß, dass dies zu der Diskussion führen kann, "sollte eine Funktion nur einen Austrittspunkt haben")

Meistens überprüfen Sie den Zeiger, tun dann, was Sie wollen, und beheben dann den Fehlerfall. Das Ergebnis kann der hässliche x-mal eingerückte Code mit mehreren ifs sein.


1
Ich persönlich hasse den Pfeilcode, der bei Verwendung eines Austrittspunkts entstehen würde. Warum nicht beenden, wenn ich die Antwort bereits kenne?
Ruslik

1
@ruslik: In C (oder C ++ mit C-with-Class-Stil) erschwert das Vorhandensein mehrerer Rückgaben die Bereinigung. In "echtem" C ++ ist dies offensichtlich kein Problem, da Ihre Bereinigung von RAII durchgeführt wird, aber einige Programmierer stecken (aus mehr oder weniger gültigen Gründen) in der Vergangenheit fest und lehnen RAII entweder ab oder dürfen sich nicht darauf verlassen .
Jalf

@jalf in solchen Fällen gotokann a sehr praktisch sein. Auch in vielen Fällen könnte eine malloc(), die bereinigt werden müsste, durch eine schnellere ersetzt werden alloca(). Ich weiß, dass sie nicht empfohlen werden, aber sie existieren aus einem bestimmten Grund (von mir ist ein gut benanntes Etikett für gotoviel sauberer als ein verschleierter if/elseBaum).
Ruslik

1
allocaist nicht standardisiert und völlig unsicher. Wenn es fehlschlägt, explodiert Ihr Programm einfach. Es besteht keine Chance auf Wiederherstellung, und da der Stapel relativ klein ist, ist ein Ausfall wahrscheinlich. Bei einem Fehler ist es möglich, dass Sie den Heap überladen und Sicherheitslücken schließen, die die Berechtigung gefährden. Verwenden allocaoder vlas niemals, es sei denn, Sie haben eine winzige Grenze für die Größe, die zugewiesen werden soll (und dann können Sie genauso gut ein normales Array verwenden).
R .. GitHub STOP HELPING ICE

4

In der Programmiersprache C (K & R) müssen Sie nach null == ptr suchen, um eine versehentliche Zuweisung zu vermeiden.


23
K & R würde auch fragen: "Was ist ein intelligenter Zeiger?" In der C ++ - Welt ändern sich die Dinge.
Alex Emelianov

2
oder besser gesagt, die Dinge haben sich bereits in der C ++ - Welt geändert ";)
Jalf

3
Wo gibt K & R das an (ref)? Sie benutzen diesen Stil nie selbst. In K & R2 Kapitel 2 empfehlen sie sogar if (!valid)oben if (valid == 0).
Schot

6
Beruhige dich, Leute. Er sagte k & r, nicht K & R. Sie sind offensichtlich weniger berühmt, da ihre Initialen nicht einmal groß geschrieben werden.
jmucchiello

1
Kapitalisierung verlangsamt Sie nur
Derek

3

Wenn Stil und Format Teil Ihrer Bewertungen sein sollen, sollte es einen vereinbarten Styleguide geben, an dem Sie messen können. Wenn es eine gibt, machen Sie, was der Styleguide sagt. Wenn es keine gibt, sollten Details wie diese so belassen werden, wie sie geschrieben sind. Es ist eine Verschwendung von Zeit und Energie und lenkt davon ab, was Codeüberprüfungen eigentlich aufdecken sollten. Im Ernst, ohne einen Styleguide würde ich grundsätzlich darauf drängen, Code wie diesen NICHT zu ändern, selbst wenn er nicht die von mir bevorzugte Konvention verwendet.

Und nicht, dass es darauf ankommt, aber meine persönliche Präferenz ist if (ptr). Die Bedeutung ist mir sofort klarer als gerade if (ptr == NULL).

Vielleicht versucht er zu sagen, dass es besser ist, Fehlerbedingungen vor dem glücklichen Weg zu behandeln? In diesem Fall stimme ich dem Rezensenten immer noch nicht zu. Ich weiß nicht, dass es dafür eine akzeptierte Konvention gibt, aber meiner Meinung nach sollte der "normalste" Zustand in jeder if-Aussage an erster Stelle stehen. Auf diese Weise muss ich weniger graben, um herauszufinden, worum es bei der Funktion geht und wie sie funktioniert.

Die Ausnahme ist, wenn der Fehler dazu führt, dass ich von der Funktion zurückkomme oder mich von ihr erholen kann, bevor ich fortfahre. In diesen Fällen behandle ich zuerst den Fehler:

if (error_condition)
  bail_or_fix();
  return if not fixed;

// If I'm still here, I'm on the happy path

Wenn ich mich vorab mit dem ungewöhnlichen Zustand befasse, kann ich mich darum kümmern und ihn dann vergessen. Aber wenn ich nicht auf dem glücklichen Weg durch den Umgang mit ihm vorne wieder aufstehen, dann sollte es behandelt werden , nachdem dem Hauptfall behandelt werden, da dies den Code verständlicher macht. Meiner Meinung nach.

Aber wenn es nicht in einem Styleguide ist, dann ist es nur meine Meinung, und Ihre Meinung ist genauso gültig. Entweder standardisieren oder nicht. Lassen Sie einen Rezensenten nicht pseudo-standardisieren, nur weil er eine Meinung hat.


1

Dies ist eine der Grundlagen beider Sprachen, die Zeiger auf einen Typ und einen Wert auswerten, die boolin C ++ und intC als Steuerausdruck verwendet werden können. Verwenden Sie ihn einfach.


1
  • Zeiger sind keine Booleschen Werte
  • Moderne C / C ++ - Compiler geben eine Warnung aus, wenn Sie versehentlich schreiben if (foo = bar).

Deshalb bevorzuge ich

if (foo == NULL)
{
    // null case
}
else
{
    // non null case
}

oder

if (foo != NULL)
{
    // non null case
}
else
{
    // null case
}

Wenn ich jedoch eine Reihe von Stilrichtlinien schreiben würde, würde ich solche Dinge nicht einfügen, sondern Dinge wie:

Stellen Sie sicher, dass Sie den Zeiger auf Null prüfen.


2
Es ist wahr, dass Zeiger keine Booleschen Werte sind, aber in C nehmen if-Anweisungen keine Booleschen Werte an: Sie verwenden ganzzahlige Ausdrücke.
Ken

@ Ken: Das liegt daran, dass C in dieser Hinsicht kaputt ist. Konzeptionell ist es ein boolescher Ausdruck und sollte (meiner Meinung nach) als solcher behandelt werden.
JeremyP

1
Einige Sprachen haben if-Anweisungen, die nur auf null / nicht null testen. Einige Sprachen haben if-Anweisungen, die nur eine Ganzzahl auf Vorzeichen testen (ein 3-Wege-Test). Ich sehe keinen Grund, C als "kaputt" zu betrachten, weil sie ein anderes Konzept gewählt haben, das Sie mögen. Es gibt viele Dinge, die ich an C hasse, aber das bedeutet nur, dass das C-Programmmodell nicht dasselbe ist wie mein mentales Modell, nicht dass einer von uns (ich oder C) kaputt ist.
Ken

@ Ken: Ein Boolescher Wert ist konzeptionell keine Zahl oder kein Zeiger. Egal in welcher Sprache.
JeremyP

1
Ich habe nicht gesagt, dass ein Boolescher Wert eine Zahl oder ein Zeiger ist. Ich sagte, es gibt keinen Grund, darauf zu bestehen, dass eine if-Anweisung nur einen booleschen Ausdruck annehmen sollte oder könnte, und bot zusätzlich zu dem vorliegenden Gegenbeispiele an. Viele Sprachen verwenden etwas anderes als einen Booleschen Wert, und zwar nicht nur in Cs "Null / Nicht-Null" -Methode. Beim Rechnen ist eine if-Anweisung, die (nur) einen Booleschen Wert akzeptiert, eine relativ junge Entwicklung.
Ken

1

Ich bin ein großer Fan von der Tatsache , dass C / C ++ überprüft nicht , Typen in den Booleschen Bedingungen in if, forund whileAussagen. Ich benutze immer folgendes:

if (ptr)

if (!ptr)

auch bei ganzen Zahlen oder anderen Typen, die in bool konvertiert werden:

while(i--)
{
    // Something to do i times
}

while(cin >> a >> b)
{
    // Do something while you've input
}

Codierung in diesem Stil ist für mich lesbarer und klarer. Nur meine persönliche Meinung.

Kürzlich habe ich bei der Arbeit am OKI 431-Mikrocontroller Folgendes festgestellt:

unsigned char chx;

if (chx) // ...

ist effizienter als

if (chx == 1) // ...

weil der Compiler im späteren Fall den Wert von chx mit 1 vergleichen muss. Dabei ist chx nur ein True / False-Flag.


1

Die meisten Compiler, die ich verwendet habe, werden zumindest ifohne weiteren Syntaxzucker vor der Zuweisung warnen , daher kaufe ich dieses Argument nicht. Das heißt, ich habe beide professionell verwendet und habe auch keine Präferenz für beide. Das == NULList aber meiner Meinung nach definitiv klarer.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.