As most Arduino users, you probably learned the C language before C++. Of course, you did! It’s the way everybody learns C++ today. While learning C, you applied the good practice of using a NULL to designate an empty pointer.

What if I tell you that using NULL is considered as a code smell for experienced C++ developers? And, what if I tell you that most C++ coding standards now forbids using NULL entirely?

Read on if you want to know why!

Why do we use NULL in the first place?

Very often, we need a way to express that a variable contains no value. In a C program, we do that by declaring a pointer and make it refer to a special address that can never point to something real: zero. For instance, we could declare an empty color string like that:

const char* color = 0;

But that wouldn’t be very clear, would it? That’s why C programmers insist on using the symbol NULL to identify an empty pointer clearly. So, to follow best practices, we should write:

const char* color = NULL;

As long as we stay in the realm of the C language, everything is fine and you can (and should) still use NULL.

What most people think NULL is?

I believe most people think that NULL is defined like that:

#define NULL ((void*)0)

Indeed, it is the right definition for the C language. It works because the language implicit converts void* to T* for any type T.

C++, however, with its strong-typing philosophy, doesn’t implicitly convert from void* to T*.

Let’s try:

#define NULL ((void*)0)
const char* color = NULL;

A C compiler accepts these two lines without any problem, but a C++ compiler returns the following error:

error: invalid conversion from 'void*' to 'const char*' [-fpermissive]

To work around this error, we would have to cast NULL explicitly:

#define NULL ((void*)0)
const char* color = reinterpret_cast<const char*>(NULL);

Ugly, isn’t it?

What NULL really is?

We saw that the definition of NULL in C++ could not be the same as the definition in C. So, what is it?

Well, it is very likely to be defined like that:

#define NULL 0

I know, it’s quite disappointing… The C++ standard allows other definitions but, from my experience, it’s often defined to 0.

Two road signs: C and C++. NULL is forbidden on the C++ road.

Why does that make a difference?

OK, NULL is not a void* but an int, so what? No big deal!

Well, maybe not a big deal, but still very annoying in certain situations.

For example:

const char *color = NULL;
String colorStr1(color);
String colorStr2(NULL);

At first sight, colorStr1 and colorStr2 look identical. So, if we compare them, they should be equal, right?

if (colorStr1== colorStr2) {
  Serial.println("colorStr1 == colorStr2");
} else {
  Serial.println("colorStr1 != colorStr2");
}

If you run this program, it displays colorStr1 != colorStr2, proving that the Strings differ.

Let’s print them:

Serial.print("colorStr1 = ");
Serial.println(colorStr1);
Serial.print("colorStr2 = ");
Serial.println(colorStr2);

These four line produces the following output:

colorStr1 =
colorStr2 = 0

Strange, isn’t it?

Problem 1: NULL messes with functions overloading

We saw that the definition of NULL exposes an inconsistency in the String class. To understand the reason, let’s look at a simpler version of the same problem. We declare two functions, like that:

void f(const char*);
void f(int);

Since these functions have the same name, they are overloads, meaning that the compiler calls one or the other depending on the type of the argument. Remember that function overloading is available in C++, but not in C.

So, guess which overload is chosen when we call:

f(NULL);

If you’re like me, your first deduction was that this line should call the overload taking a char-pointer. However, after what we saw earlier, you should understand why it calls the other overload. Indeed, as far as the compiler is concerned, it’s identical to calling f() like that:

f(0);

How is that related to the String problem? Simple! By passing NULL to the constructor, we just called the wrong overload, the one that takes an integer and converts it to a string.

Problem 2: NULL messes with template type deduction

We saw that the strange definition of NULL pushes the compiler to select the wrong overload but is it the only problem? Unfortunately no, it also confuses the template type deduction. Imaging we declare this template function:

template<typename T>
void printValue(T value);

Then, we specialize it for integer and pointer:

template <>
void printValue<int>(int value) {
  Serial.print("Integer: ");
  Serial.println(value, DEC);
}

template <>
void printValue<void *>(void *value) {
  Serial.print("Pointer: 0x");
  Serial.println((intptr_t)value, HEX);
}

Now, if we call printValue(NULL), it will print:

Integer: 0

I know there are better ways to solve this problem; I show you this example because it illustrates the problem with NULL. With this program, we see that the compiler deduces the template type to be an integer, while we imagined it would be some pointer type.

Let’s see how this type deduction thing can affect your program on the real life. Imaging you use ArduinoJson 5, and you write something like that:

DynamicJsonBuffer jb;
JsonObject& obj= jb.parseObject("{\"id\":0}");
if (obj["id"] == NULL) {
  Serial.println("ERROR: id is missing");
}

This program displays an error, even if the key id is present in the object, because the compiler believes you wrote:

if (obj["id"] == 0) {

So, you see, this template type deduction can have real implications.

By the way, if you want to fix this program, see JsonObject::containsKey().

The solution

I told you that C++ programmers banned NULL from their code-base, but what do they use instead?

Instead of NULL, they use nullptr, a new keyword introduced in C++11.

  • Like NULL, nullptr implicitly converts to T* for any type T.
  • Unlike NULL, nullptr is not an integer so it cannot call the wrong overload.
  • Unlike NULL, nullptr has its own type, nullptr_t, so the compiler makes correct type deductions.

Are there any drawbacks to using nullptr instead of NULL? No, unless you target old compilers that don’t support C++11, which is very unlikely.

Mitigations

Before wrapping up, I’d like to clarify something I skipped to simplify this article.

The C++ standard committee knows there is a lot of existing code using NULL that would become safer if it was using nullptr instead. So, in the C++11 standard, they allowed the definition of NULL to be:

// either
#define NULL 0
// or
#define NULL nullptr
// both are legal in C++11

From my experience, the standard library defines NULL as 0, not as nullptr. However, what the C++11 standard allows us it to replace it in our code-base to make our old code safer with a single line in the right header.

Compiler writers are also well aware of the problems with NULL, so they implemented a special case allowing them to issue a warning when appropriate. For example, when we call f(NULL) as we did earlier, the compiler produces the following warning:

warning: passing NULL to non-pointer argument 1 of 'void f(int, int)' [-Wconversion-null]

You always look at the warnings, right?

Conclusion

I could have written this article with just one single line:

Don’t use NULL, use nullptr instead

That’s not the way I work. I don’t just tell people what to do; I explain why they should do it. Understanding the rationale behind the language features is what will make you a great C++ developer, so stay tuned to read more articles like this one.

By the way, if you want to practice at home, download the samples for this article on github.com/bblanchon/cpp4arduino.

See you soon!