Why are NULL pointers defined differently in C and C++?

Back in C++03, a null pointer was defined by the ISO specification (§4.10/1) as

A null pointer constant is an integral constant expression (5.19) rvalue of integer type that evaluates to zero.

This is why in C++ you can write

int* ptr = 0;

In C, this rule is similar, but is a bit different (§6.3.2.3/3):

An integer constant expression with the value 0, or such an expression cast to type
void *, is called a null pointer constant.55) If a null pointer constant is converted to a
pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal
to a pointer to any object or function.

Consequently, both

int* ptr = 0;

and

int* ptr = (void *)0

are legal. However, my guess is that the void* cast is here so that statements like

int x = NULL;

produce a compiler warning on most systems. In C++, this wouldn’t be legal because you can’t implicitly convert a void* to another pointer type implicitly without a cast. For example, this is illegal:

int* ptr = (void*)0; // Legal C, illegal C++

However, this leads to issues because the code

int x = NULL;

is legal C++. Because of this and the ensuing confusion (and another case, shown later), since C++11, there is a keyword nullptr representing a null pointer:

int* ptr = nullptr;

This doesn’t have any of the above problems.

The other advantage of nullptr over 0 is that it plays better with the C++ type system. For example, suppose I have these two functions:

void DoSomething(int x);
void DoSomething(char* x);

If I call

DoSomething(NULL);

It’s equivalent to

DoSomething(0);

which calls DoSomething(int) instead of the expected DoSomething(char*). However, with nullptr, I could write

DoSomething(nullptr);

And it will call the DoSomething(char*) function as expected.

Similarly, suppose that I have a vector<Object*> and want to set each element to be a null pointer. Using the std::fill algorithm, I might try writing

std::fill(v.begin(), v.end(), NULL);

However, this doesn’t compile, because the template system treats NULL as an int and not a pointer. To fix this, I would have to write

std::fill(v.begin(), v.end(), (Object*)NULL);

This is ugly and somewhat defeats the purpose of the template system. To fix this, I can use nullptr:

std::fill(v.begin(), v.end(), nullptr);

And since nullptr is known to have a type corresponding to a null pointer (specifically, std::nullptr_t), this will compile correctly.

Hope this helps!

Leave a Comment