NULL und Zero - verflixt und verpointert!
-
Hallo,
vor vielen Jahren liess ich mich dazu hinreissen, 0 statt NULL zu schreiben, wenn ein Pointer mit Null verglichen oder initialisiert werden sollte. Noch immer verfolgen mich diese Source-Code-Teile.Ich habe z.B. die Funktion CPStrDupArg(const char*, ...), wo als letzter Parameter eine NULL gesetzt werden muß. Wenn ich da mal statt NULL eine 0 gesetzt hatte, scheint mir das gelegentlich eine Ursache für Segfaults zu sein. Denn jedesmal, wenn ich da NULL hinschreibe, ist der Segfault weg.
Kann das irgendjemand bestätigen?
Mein Compiler ist gcc 4.7.2 auf Debian Wheezy und das ganze in 64 Bit.
-
Ist das wirklich eine Funktion mit Variadic Arguments?
Und sicher, dass das C++ ist?
In C++ ist NULL nämlich einfach nur 0, weswegen das keinen Unterschied machen sollte.
In C wäre das aber schon ein Unterschied, das ist NULL nämlich ein Pointer, während 0 keiner ist, was beim internen reinterpret cast der C-Var-Args zu Problemen führen könnte.
-
NULL ist in C++ ein Makro für 0 (oder 0L oder vergleichbares*). Das heißt, es kann keinen Unterschied zwischen 0 und NULL geben, da es das gleiche ist (außer eben in ganz exotischen Fällen, wo zum Beispiel der Typ eines Ausdrucks wichtig wird und ein
#define NULL 0Leben doch einen kleinen Unterschied zu 0 macht). Außerdem gibt es in C++ sowieso nullptr, welcher in der Regel bevorzugt werden sollte.
In C gibt es einen kleinen Unterschied, da NULL einen Pointertyp hat (in der Regel etwas wie#define NULL (void*)0), was dann ebenfalls selten mal wichtig werden kann.edit: Zu langsam.
*: Oder neuerdings auch nullptr.
-
Der GCC definiert NULL meines wissens als __null, was Warnungen ausgibt, wenn man es zu einem int konvertiert.
-
Also unter 64Bit Linux ist
0ein 32Bit int und0Lein 64Bit long. Von daher gibt es da schon einen Unterschied. Ich glaube aber nicht dass ein 32Bit Wert auch nur mit 32Bit auf den Stack gepush'd wird. Möglicherweise enthalten die höchstwertigen 32Bit aber Garbage? Wäre mit einer kleinen Testfunktion zu klären.- osdt
-
osdt schrieb:
Also unter 64Bit Linux ist
0ein 32Bit int und0Lein 64Bit long. Von daher gibt es da schon einen Unterschied. Ich glaube aber nicht dass ein 32Bit Wert auch nur mit 32Bit auf den Stack gepush'd wird. Möglicherweise enthalten die höchstwertigen 32Bit aber Garbage? Wäre mit einer kleinen Testfunktion zu klären.Funktionsargumente funktionieren so nicht. Sonst wären Funktionen ja völlig unbenutzbar.
-
SeppJ schrieb:
osdt schrieb:
Also unter 64Bit Linux ist
0ein 32Bit int und0Lein 64Bit long. Von daher gibt es da schon einen Unterschied. Ich glaube aber nicht dass ein 32Bit Wert auch nur mit 32Bit auf den Stack gepush'd wird. Möglicherweise enthalten die höchstwertigen 32Bit aber Garbage? Wäre mit einer kleinen Testfunktion zu klären.Funktionsargumente funktionieren so nicht. Sonst wären Funktionen ja völlig unbenutzbar.
Wie meinst Du das? Bei Variadic Argumenten gibt es keinen vordefinierten Datentyp, also ist 0 und 0L schon ein Unterschied.
- osdt
-
SeppJ schrieb:
osdt schrieb:
Also unter 64Bit Linux ist
0ein 32Bit int und0Lein 64Bit long. Von daher gibt es da schon einen Unterschied. Ich glaube aber nicht dass ein 32Bit Wert auch nur mit 32Bit auf den Stack gepush'd wird. Möglicherweise enthalten die höchstwertigen 32Bit aber Garbage? Wäre mit einer kleinen Testfunktion zu klären.Funktionsargumente funktionieren so nicht. Sonst wären Funktionen ja völlig unbenutzbar.
Der Hinweis ist zutreffend (wenn wir über x86_64 reden). Praktisch werden Parameter meist per Register übergeben und durch die implizite Null-Erweiterung bei 32bit-loads sind die oberen 32 bits des 64-bit Register i.d.R. Null. Die ABI garantiert das allerdings nicht (mit einer einzigen Ausnahme: im ILP32-Modell werden die 32-bit Zeiger zero-extended übergeben). Und bei Übergabe über den Stack ist es auch praktisch nicht immer der Fall.
-
camper schrieb:
SeppJ schrieb:
osdt schrieb:
Also unter 64Bit Linux ist
0ein 32Bit int und0Lein 64Bit long. Von daher gibt es da schon einen Unterschied. Ich glaube aber nicht dass ein 32Bit Wert auch nur mit 32Bit auf den Stack gepush'd wird. Möglicherweise enthalten die höchstwertigen 32Bit aber Garbage? Wäre mit einer kleinen Testfunktion zu klären.Funktionsargumente funktionieren so nicht. Sonst wären Funktionen ja völlig unbenutzbar.
Der Hinweis ist zutreffend (wenn wir über x86_64 reden). Praktisch werden Parameter meist per Register übergeben und durch die implizite Null-Erweiterung bei 32bit-loads sind die oberen 32 bits des 64-bit Register i.d.R. Null. Die ABI garantiert das allerdings nicht (mit einer einzigen Ausnahme: im ILP32-Modell werden die 32-bit Zeiger zero-extended übergeben). Und bei Übergabe über den Stack ist es auch praktisch nicht immer der Fall.
Du willst damit doch wohl nicht etwa sagen, dass folgendes undefiniert wäre?void foo(long); foo(0);osdt schrieb:
Wie meinst Du das? Bei Variadic Argumenten gibt es keinen vordefinierten Datentyp, also ist 0 und 0L schon ein Unterschied.
Ein Unterschied, aber keine Mülldaten. Dieser Unterschied wurde schon in den ersten paar Antworten erwähnt und trifft hier eventuell tatsächlich auf, da der TE anscheinend eine Funktion mit variadischen Argumenten vorliegen hat. Falls diese dann die Parameter inkorrekt verarbeitet (z.B. bei printf %d oder %ld zur Ausgabe von Pointern nutzen), kann es durchaus zu Fehlern kommen.
-
Antwort für C++: Mehr Argumente auszulesen als vorhanden sind hat undefiniertes Verhalten.
Antwort für GCC: Die Argumente in der Ellipse (...) werden natürlich über den Stack übergeben, denn die Register sind sehr knapp. Wenn die aufgerufene Funktion das Zeigerargument liest, um es mit
nullptrzu vergleichen, setzt sie diesen Zeiger aus demint0 vom Stack und irgendwelchen Bytes zusammen, die zufällig daneben liegen. Einintist auf der Plattform schließlich kleiner als ein Zeiger. Die Daten neben demintsind wahrscheinlich ungleich Null und somit wird der zusammengesetzte Zeiger nicht alsnullptrerkannt. Die Funktion erkennt das letzte Argument als gültigen Zeiger und dereferenziert diesen vermutlich. Bei dem Speicherzugriff beschwert sich dann der Prozessor über den ungültigen Zeiger.Gute Lösung: Keine C-Ellipse verwenden. Du hast einen Compiler, der die sichere Alternative aus C++11 beherrscht: Das variadic template.
Schlechte Lösung: Durchgängig
nullptrverwenden.
-
Hallo,
vielen Dank an alle!Ich habe die o.g. Funktion in C geschrieben. Aus C++-Quellen wird diese C-Funktion aber aufgerufen.