Warum sind NULL-Zeiger in C und C ++ unterschiedlich definiert?
73
In C NULList definiert als (void *)0in C ++ 0. Wieso ist es so? In CI kann man verstehen, dass Compiler möglicherweise Warnungen generieren , wenn sie NULLnicht typisiert werden (void *). Gibt es einen anderen Grund?
Ein interessanter Unterschied besteht darin, dass Sie in C implizit void*in einen beliebigen Zeigertyp umwandeln können und in C ++ nicht.
Asveikau
2
Eine Implementierung von C oder C ++ kann rechtlich definiert werden NULLals 0. Die AC-Implementierung kann es als ((void*)0), aber nicht als definieren (void*)0. Ein in Standard-C-Headern definiertes Makro muss durch Klammern geschützt werden, damit es so verwendet werden kann, als wäre es ein einzelner Bezeichner. mit wäre #define NULL (void*)0der Ausdruck sizeof NULLein Syntaxfehler.
Keith Thompson
Antworten:
103
Zurück in C ++ 03 wurde ein Nullzeiger durch die ISO-Spezifikation (§4.10 / 1) als definiert
Eine Nullzeigerkonstante ist ein integraler Konstantenausdruck (5.19) rWert vom Integer-Typ, der zu Null ausgewertet wird.
Deshalb können Sie in C ++ schreiben
int* ptr = 0;
In C ist diese Regel ähnlich, aber etwas anders (§6.3.2.3 / 3):
Ein ganzzahliger Konstantenausdruck mit dem Wert 0 oder ein solcher Ausdruck, der in einen Typ umgewandelt wird
void *, wird als Nullzeigerkonstante bezeichnet.55) Wenn eine Nullzeigerkonstante in einen Zeigertyp konvertiert wird, wird der resultierende Zeiger, der als Nullzeiger bezeichnet wird, garantiert Vergleiche ungleich einem Zeiger auf ein Objekt oder eine Funktion.
Folglich beides
int* ptr = 0;
und
int* ptr = (void *)0
sind legal. Ich vermute jedoch, dass die void*Besetzung hier ist, so dass Aussagen mögen
int x = NULL;
Erstellen Sie auf den meisten Systemen eine Compiler-Warnung. In C ++ wäre dies nicht legal, da Sie a ohne Umwandlung nicht implizit in einen void*anderen Zeigertyp konvertieren können . Dies ist beispielsweise illegal:
int* ptr = (void*)0; // Legal C, illegal C++
Dies führt jedoch zu Problemen aufgrund des Codes
int x = NULL;
ist legal C ++. Aus diesem Grund und der daraus resultierenden Verwirrung (und einem anderen Fall, der später gezeigt wird) gibt es seit C ++ 11 ein Schlüsselwort, nullptrdas einen Nullzeiger darstellt:
int* ptr = nullptr;
Dies hat keines der oben genannten Probleme.
Der andere Vorteil von nullptrüber 0 ist, dass es mit dem System vom Typ C ++ besser spielt. Angenommen, ich habe diese beiden Funktionen:
voidDoSomething(int x);
voidDoSomething(char* x);
Wenn ich anrufe
DoSomething(NULL);
Es ist gleichbedeutend mit
DoSomething(0);
das ruft DoSomething(int)statt der erwarteten DoSomething(char*). Mit nullptrkonnte ich jedoch schreiben
DoSomething(nullptr);
Und es wird die DoSomething(char*)Funktion wie erwartet aufrufen .
Angenommen, ich habe ein vector<Object*>und möchte jedes Element als Nullzeiger festlegen. Mit dem std::fillAlgorithmus könnte ich versuchen zu schreiben
std::fill(v.begin(), v.end(), NULL);
Dies wird jedoch nicht kompiliert, da das Vorlagensystem NULLals intund nicht als Zeiger behandelt wird. Um dies zu beheben, müsste ich schreiben
std::fill(v.begin(), v.end(), (Object*)NULL);
Dies ist hässlich und macht den Zweck des Vorlagensystems etwas zunichte. Um dies zu beheben, kann ich verwenden nullptr:
std::fill(v.begin(), v.end(), nullptr);
Und da nullptrbekannt ist, dass ein Typ einem Nullzeiger entspricht (speziell std::nullptr_t), wird dieser korrekt kompiliert.
Schauen Sie sich die Antwort von @Keith Thompsons an. Eine "Nullzeigerkonstante" in C ist ein konstanter ganzzahliger Ausdruck eines Werts, der 0optional umgewandelt wird void*.
Jens Gustedt
@Jens Gustedt- Kannst du meine bearbeitete Antwort überprüfen, um festzustellen, ob sie korrekter ist?
Templatetypedef
3
fast perfekt! Nur eine Kleinigkeit, Sie scheinen immer noch zu implizieren, dass NULLdies (void*)0in C sein muss . Dies ist nicht der Fall. Leider kann es nur für eine beliebige Nullzeigerkonstante definiert werden, so dass es in C kein sehr zuverlässiges Konzept ist. Daher denke ich persönlich, dass dies NULLeinfach vermieden werden sollte. Und der Grund, warum einige es vorziehen, vom Zeigertyp zu sein, sind eher die Regeln für die implizite Argumentförderung zu Funktionen ohne Prototyp, nämlich va_arg-Argumentlisten. Aber das ist eine andere Geschichte ...
Jens Gustedt
17
NULLErweitert in C auf eine implementierungsdefinierte "Nullzeigerkonstante". Eine Nullzeigerkonstante ist entweder ein ganzzahliger Konstantenausdruck mit dem Wert 0 oder ein solcher Ausdruck, in den umgewandelt wird void*. Eine C-Implementierung kann also NULLentweder als 0oder als definieren ((void*)0).
In C ++ unterscheiden sich die Regeln für Nullzeigerkonstanten. Insbesondere ((void*)0)handelt es sich nicht um eine C ++ - Nullzeigerkonstante, sodass eine C ++ - Implementierung diese nicht definieren NULLkann.
Die Sprache C wurde erstellt, um die Programmierung von Mikroprozessoren zu vereinfachen. Der AC-Zeiger wird verwendet, um die Adresse der Daten im Speicher zu speichern. Es war ein Weg erforderlich, um darzustellen, dass ein Zeiger keinen gültigen Wert hatte. Die Adresse Null wurde gewählt, da alle Mikroprozessoren diese Adresse zum Booten verwendeten. Da es für nichts anderes verwendet werden konnte, war Null eine gute Wahl, um einen Zeiger ohne gültigen Wert darzustellen. C ++ ist abwärtskompatibel mit C, daher hat es diese Konvention geerbt.
Die Anforderung, bei Verwendung als Zeiger Null zu setzen, ist nur ein neues Add-On. Spätere Generationen von C wollten mehr Genauigkeit (und hoffentlich weniger Fehler), damit sie die Syntax pedantischer angehen.
Woher bekommen Sie, dass es erforderlich ist, 0in einen Zeigertyp umzuwandeln. Weder C noch C ++ haben eine solche Regel. In C Sie können haben (void*)0als Null - Zeiger - Konstante, aber nichts zwingt Sie , dies zu tun.
Jens Gustedt
3
Ich denke, die Beweislast geht in die andere Richtung - der Standard sagt nirgendwo, dass der Nullzeiger eine bestimmte Darstellung haben muss. Suchen Sie nach "Nullzeigerdarstellung" und Sie werden sehen, dass es unzählige Male besprochen wird. Beachten Sie, dass die Konstante 0in der Quelle eine Nullzeigerkonstante sein muss - dies erfordert jedoch nicht, dass die interne Darstellung des Nullzeigers alle Bits Null ist und dass sie nicht mit der Darstellung der Ganzzahl Null übereinstimmt.
Ymett
2
Ein Zeiger wird auf irgendeine Weise im Speicher dargestellt (außer wenn der Compiler ihn optimiert). Diese Darstellung ist im Quellcode nicht vorhanden. Es gibt also definitiv "eine versteckte interne Repräsentation". Die normale Darstellung ist einfach die Adresse des Objekts, auf das verwiesen wird. Dies funktioniert offensichtlich nicht für einen Nullzeiger, da auf kein Objekt verwiesen wird, sodass der Compiler etwas anderes verwenden muss. Alle Bits Null werden oft verwendet, aber der Standard verlangt dies nirgendwo (und es gab Compiler, die etwas anderes verwendeten).
Ymett
3
@ Jay: Ich habe ein System verwendet, das den Wert 1 für einen Nullzeiger verwendet hat, weil die CPU einen Verweis auf eine ungerade Adresse abfangen würde. Zugegeben, dies war kein C-Compiler, aber die Verwendung einer Nicht-Null-Darstellung für Nullzeiger hatte einen echten Vorteil. Keiner von uns weiß, wie zukünftige Systeme aussehen werden, aber wir haben bereits Massenumschreibungen gesehen, die durch ähnliche Änderungen erforderlich sind, wie z. B. Code, der davon abhängt *(int*)0 == 0. Und das Schreiben von Code, der keinen Nullzeiger voraussetzt, ist nicht so schwierig.
Keith Thompson
2
@ Jay: Du hast die Wahl. Sie können Code schreiben, der auf Systemen fehlschlägt, auf denen ein Nullzeiger nicht alle Bits Null ist. Dieser Code funktioniert wahrscheinlich auf den meisten Systemen ordnungsgemäß. Oder Sie können Code schreiben, der keine Annahmen darüber macht, wie Nullzeiger dargestellt werden. Dieser Code funktioniert auf allen Systemen ordnungsgemäß . Im letzteren Fall, wenn (nicht wenn) sich Ihr Programm schlecht verhält, haben Sie einen möglichen Grund weniger, sich Sorgen zu machen. Und hier ist der lustige Teil: Die zweite Alternative ist nicht schwieriger als die erste.
void*
in einen beliebigen Zeigertyp umwandeln können und in C ++ nicht.NULL
als0
. Die AC-Implementierung kann es als((void*)0)
, aber nicht als definieren(void*)0
. Ein in Standard-C-Headern definiertes Makro muss durch Klammern geschützt werden, damit es so verwendet werden kann, als wäre es ein einzelner Bezeichner. mit wäre#define NULL (void*)0
der Ausdrucksizeof NULL
ein Syntaxfehler.Antworten:
Zurück in C ++ 03 wurde ein Nullzeiger durch die ISO-Spezifikation (§4.10 / 1) als definiert
Deshalb können Sie in C ++ schreiben
int* ptr = 0;
In C ist diese Regel ähnlich, aber etwas anders (§6.3.2.3 / 3):
Folglich beides
int* ptr = 0;
und
int* ptr = (void *)0
sind legal. Ich vermute jedoch, dass die
void*
Besetzung hier ist, so dass Aussagen mögenint x = NULL;
Erstellen Sie auf den meisten Systemen eine Compiler-Warnung. In C ++ wäre dies nicht legal, da Sie a ohne Umwandlung nicht implizit in einen
void*
anderen Zeigertyp konvertieren können . Dies ist beispielsweise illegal:int* ptr = (void*)0; // Legal C, illegal C++
Dies führt jedoch zu Problemen aufgrund des Codes
int x = NULL;
ist legal C ++. Aus diesem Grund und der daraus resultierenden Verwirrung (und einem anderen Fall, der später gezeigt wird) gibt es seit C ++ 11 ein Schlüsselwort,
nullptr
das einen Nullzeiger darstellt:int* ptr = nullptr;
Dies hat keines der oben genannten Probleme.
Der andere Vorteil von
nullptr
über 0 ist, dass es mit dem System vom Typ C ++ besser spielt. Angenommen, ich habe diese beiden Funktionen:void DoSomething(int x); void DoSomething(char* x);
Wenn ich anrufe
DoSomething(NULL);
Es ist gleichbedeutend mit
DoSomething(0);
das ruft
DoSomething(int)
statt der erwartetenDoSomething(char*)
. Mitnullptr
konnte ich jedoch schreibenDoSomething(nullptr);
Und es wird die
DoSomething(char*)
Funktion wie erwartet aufrufen .Angenommen, ich habe ein
vector<Object*>
und möchte jedes Element als Nullzeiger festlegen. Mit demstd::fill
Algorithmus könnte ich versuchen zu schreibenstd::fill(v.begin(), v.end(), NULL);
Dies wird jedoch nicht kompiliert, da das Vorlagensystem
NULL
alsint
und nicht als Zeiger behandelt wird. Um dies zu beheben, müsste ich schreibenstd::fill(v.begin(), v.end(), (Object*)NULL);
Dies ist hässlich und macht den Zweck des Vorlagensystems etwas zunichte. Um dies zu beheben, kann ich verwenden
nullptr
:std::fill(v.begin(), v.end(), nullptr);
Und da
nullptr
bekannt ist, dass ein Typ einem Nullzeiger entspricht (speziellstd::nullptr_t
), wird dieser korrekt kompiliert.Hoffe das hilft!
quelle
0
optional umgewandelt wirdvoid*
.NULL
dies(void*)0
in C sein muss . Dies ist nicht der Fall. Leider kann es nur für eine beliebige Nullzeigerkonstante definiert werden, so dass es in C kein sehr zuverlässiges Konzept ist. Daher denke ich persönlich, dass diesNULL
einfach vermieden werden sollte. Und der Grund, warum einige es vorziehen, vom Zeigertyp zu sein, sind eher die Regeln für die implizite Argumentförderung zu Funktionen ohne Prototyp, nämlich va_arg-Argumentlisten. Aber das ist eine andere Geschichte ...NULL
Erweitert in C auf eine implementierungsdefinierte "Nullzeigerkonstante". Eine Nullzeigerkonstante ist entweder ein ganzzahliger Konstantenausdruck mit dem Wert 0 oder ein solcher Ausdruck, in den umgewandelt wirdvoid*
. Eine C-Implementierung kann alsoNULL
entweder als0
oder als definieren((void*)0)
.In C ++ unterscheiden sich die Regeln für Nullzeigerkonstanten. Insbesondere
((void*)0)
handelt es sich nicht um eine C ++ - Nullzeigerkonstante, sodass eine C ++ - Implementierung diese nicht definierenNULL
kann.quelle
Die Sprache C wurde erstellt, um die Programmierung von Mikroprozessoren zu vereinfachen. Der AC-Zeiger wird verwendet, um die Adresse der Daten im Speicher zu speichern. Es war ein Weg erforderlich, um darzustellen, dass ein Zeiger keinen gültigen Wert hatte. Die Adresse Null wurde gewählt, da alle Mikroprozessoren diese Adresse zum Booten verwendeten. Da es für nichts anderes verwendet werden konnte, war Null eine gute Wahl, um einen Zeiger ohne gültigen Wert darzustellen. C ++ ist abwärtskompatibel mit C, daher hat es diese Konvention geerbt.
Die Anforderung, bei Verwendung als Zeiger Null zu setzen, ist nur ein neues Add-On. Spätere Generationen von C wollten mehr Genauigkeit (und hoffentlich weniger Fehler), damit sie die Syntax pedantischer angehen.
quelle
0
in einen Zeigertyp umzuwandeln. Weder C noch C ++ haben eine solche Regel. In C Sie können haben(void*)0
als Null - Zeiger - Konstante, aber nichts zwingt Sie , dies zu tun.0
in der Quelle eine Nullzeigerkonstante sein muss - dies erfordert jedoch nicht, dass die interne Darstellung des Nullzeigers alle Bits Null ist und dass sie nicht mit der Darstellung der Ganzzahl Null übereinstimmt.*(int*)0 == 0
. Und das Schreiben von Code, der keinen Nullzeiger voraussetzt, ist nicht so schwierig.