Not NULL
« | 20 Jun 2021 | »Beim Stöbern in alten Codes, habe ich mich wieder an eine alte “Weisheit” erinnert, die ich oft verbreitet habe:
Nutze stets das
NULL
Makro, um Pointer zu prüfen.
Für reine C++ Entwickler ist das
natürlich hinfällig (siehe nullptr
) … doch wenn man seine Gebete (auch)
an die Mutter C
richtet, sieht die Sache ein wenig anders aus.
Wenn man in den C/C++ Headern herumwühlt, findet man meist (aber nicht immer) folgende Implementierung
In C++ hat die Zahlenkonstante 0
eine ganz besondere Bedeutung. Sie wird
nämlich in genau den integralen oder Pointer-Typ konvertiert, der von
ihr erwartet wird.
Sowohl int x = 0;
als auch int* x = 0;
sind valider C++ Code, obwohl
die Nullen zu etwas ganz anderem werden. Im ersten Fall eine Ganzzahl und im
zweiten Fall ein zur Null-Adresse zeigender Pointer vom Typ int
.
In C
ist man da eigentlich nicht so tolerant und erwartet, dass eine 0
nur eine Ganzzahl ist. Ein Pointer ließe sich damit zwar auch initialisieren,
aber der Compiler müsste dann mindestens eine Warnung werfen, dass eine
implizite (möglicherweise falsche) Typenkonvertierung stattgefunden hat.
(Ältere Compiler verzichten darauf allerdings und frühere Standards waren
da auch wesentlich toleranter bzw. ignoranter.)
Und genau deshalb existiert das NULL
Makro, denn dieses erzeugt eine
Null-Pointer-Konstante durch einen expliziten Cast der Null-Konstante (0
)
auf einen generischen Pointer.
Genau hier gibt es wieder einen Unterschied zu C++, denn in C sollten alle Datenzeiger implizit zu generischen Pointer konvertiert werden können und umgekehrt.
Daraus folgt, dass der Code type_t* ptr = NULL;
universell gültig ist
und in C dann zu einem type_t* ptr = (type_t*)((void*)0);
wird,
während C++ type_t* ptr = (type_t*)0;
direkt verstehen kann.
Würde man stattdessen type_t* ptr = 0;
schreiben, gäbe es unter C
eine Warnung. Schriebe man andererseits type_t* ptr = ((void*)0);
,
würde C++ die Pointer-Konvertierung nicht
(oder nur mit Bauchschmerzen == Warnungen) durchgehen lassen.
NULL != 0 ?
Schon vor langer Zeit hörte ich, dass der Wert NULL
etwas ganz anderes
sein kann und nicht zwingend in Zusammenhang mit der Null-Konstante (0
)
steht. Es könnte auch sein, dass NULL
so definiert ist:
#define NULL ((void*)-1)
Da würde mir auch gleich eine alte Plattform einfallen, wo das sinnvoll wäre:
nämlich DOS. Denn der dort aktive
X86-Real-Mode legt an der Speicheradress 0
seine Interrupt-Vektor-Tabelle ab.
Ein Zugriff auf diese Adresse per Pointer ist erlaubt und so könnte man
unabsichtlich beim Schreiben auf einen 0
-basierten Null-Pointer diese
Tabelle korrumpieren.
Ein NULL
mit -1
könnte das verhindern.
Da würde es also Sinn machen, das NULL
Makro auf einen Wert zu setzen,
der einen ungültigen Speicherbereich adressiert.
Mal schnell ins Archiv geguckt und …
Nein, Turbo C++ 1.0 und
3.0 für DOS definieren NULL
so:
Und das alte Microsoft C 5.1 tat das damals auch so,
ebenfalls ohne void*
Cast.
Im C Standard
heißt es aber generell in 6.3.2.3, dass eine nach einem Pointer-Typ
gecastete Null-Konstante (also die Zahl 0
), offiziell ein Null-Pointer
ist, mit all seinen Rechten und Pflichten. In 7.17 wird das NULL
Makro
lediglich als implementierungsspezifische Null-Pointer Konstante beschrieben.
Es ist nicht definiert wie es aussehen soll, aber ein (NULL == ((void*)0))
sollte am Ende true
zurückgeben.
int* ptr = (int*)0;
oder int* ptr = (void*)0;
sind also in C korrekt und
ein Null-Pointer, genau so wie int* ptr = NULL;
auch.
Fazit
Nun, ich werde weiter versuchen, dass NULL
Makro einzusetzen, vor allem,
um die Kompatibilität zwischen C und C++ Code zu gewährleisten.
Ich habe aber früher gerne mal behauptet:
NULL
kann in C auch ungleich(void*)0
sein und deshalb sollte auch inif
Statements immer gegenNULL
verglichen werden. Es sollte also immerif(NULL != ptr)
benutzt werden und keine anderen Formen.
Meine Implikation, dass if(ptr)
fehlerhaft sein könnte und if(NULL != ptr)
immer korrekt wäre, ist jedenfalls nicht richtig.
Wie auch immer … lesbarer ist der Vergleich mit NULL
aus meiner
Sicht aber schon, und schadet jedenfalls nicht.
Tja … den Standard zu lesen und zu verstehen ist immer eine gute Idee. Und ich habe wieder mal gelernt, dass nur weil man 10 Jahre glaubt, dass etwas so oder so sei, heißt das noch lange nicht, dass das auch stimmt.