Not NULL

Beim Stöbern in alten Codes, habe ich mich wieder an eine alte “Weißheit” 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

1#ifdef __cplusplus
2#  define NULL 0
3#else
4#  define NULL ((void *)0)
5#endif

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 tollerant 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 tolleranter 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 Datenpointer implizit zu generischen Pointer konvertiert werden können und umgekehrt.

1int dummy = 42;
2int* ptr_dummy = &dummy;
3void* ptr_generic = ptr_dummy; // valid everywhere
4int* ptr_alias = ptr_generic;  // valid in C, invalid in C++

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:

1#if defined(__TINY__) || defined(__SMALL__) || defined(__MEDIUM__)
2#  define NULL    0
3#else
4#  define NULL    0L
5#endif

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 in if Statements immer gegen NULL verglichen werden. Es sollte also immer if(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.


Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!

Meine Dokus über:
 
Externe Links zu: