According to §6.3.2.3 ¶3 of the C11 standard, a null pointer constant in C can be defined by an implementation as either the integer constant expression 0
or such an expression cast to void *
. In C the null pointer constant is defined by the NULL
macro.
My implementation (GCC 9.4.0) defines NULL
in stddef.h
in the following ways:
#define NULL ((void *)0)
#define NULL 0
Why are both of the above expressions considered semantically equivalent in the context of NULL
? More specifically, why do there exist two ways of expressing the same concept rather than one?
CodePudding user response:
Let's consider this example code:
#include <stddef.h>
int *f(void) { return NULL; }
int g(int x) { return x == NULL ? 3 : 4; }
We want f to compile without warnings, and we want g to cause an error or a warning (because an int
variable x was compared to a pointer).
In C, #define NULL ((void*)0)
gives us both (GCC warning for g, clean compile for f).
However, in C , #define NULL ((void*)0)
causes a compile error for f. Thus, to make it compile in C , <stddef.h> has #define NULL 0
for C only (not for C). Unfortunately, this also prevents the warning from being reported for g. To fix that, C 11 uses built-in nullptr
instead of NULL
, and with that, C compilers report an error for g, and they compile f cleanly.
CodePudding user response:
((void *)0)
has stronger typing and could lead to better compiler or static analyser diagnostics. For example since implicit conversions between pointers and plain integers aren't allowed in standard C.
0
is likely allowed for historical reasons, from a pre-standard time when everything in C was pretty much just integers and wild implicit conversions between pointers and integers were allowed, though possibly resulting in undefined behavior.
Ancient K&R 1st edition provides some insight (7.14 the assignment operator):
The compilers currently allow a pointer to be assigned to an integer, an integer to a pointer, and a pointer to a pointer of another type. The assignment is a pure copy operation, with no conversion. This usage is nonportable, and may produce pointers which cause addressing exceptions when used. However, it is guaranteed that assignment of the constant 0 to a pointer will produce a null pointer distinguishable from a pointer to any object.