Home > Back-end >  Why are there two ways of expressing NULL in C?
Why are there two ways of expressing NULL in C?

Time:12-22

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.

  • Related