Can '\0' and NULL be used interchangeably?
C++NullC++ Problem Overview
NULL is often used in the context of pointers, and is defined via macros in multiple standard libraries (such as <iostream>
) to be the integer 0
. '\0'
is the null character, and is 8 bits of zeros. Incidentally, 8 bits of zeros is equivalent to the integer 0
.
In some cases, although it is considered to be horrible style, these two can be interchanged:
int *p='\0';
if (p==NULL) //evaluates to true
cout << "equal\n";
Or
char a=NULL;
char b='\0';
if (a==b) //evaluates to true
cout << "equal again\n";
There are already many similar questions on SO alone; for example, the top answer for this question (https://stackoverflow.com/questions/1296843/what-is-the-difference-between-null-0-and-0) says "they are not really the same thing."
Could anyone provide an example that NULL
and \0
cannot be interchanged (preferably an actual application and not a pathological case)?
C++ Solutions
Solution 1 - C++
> Could anyone provide an example that NULL and \0 cannot be interchanged?
The difference between NULL
and '\0'
may affect overload resolution.
Example (check it on Coliru):
#include <iostream>
// The overloaded function under question can be a constructor or
// an overloaded operator, which would make this example less silly
void foo(char) { std::cout << "foo(char)" << std::endl; }
void foo(int) { std::cout << "foo(int)" << std::endl; }
void foo(long) { std::cout << "foo(long)" << std::endl; }
void foo(void*) { std::cout << "foo(void*)" << std::endl; }
int main()
{
foo('\0'); // this will definitely call foo(char)
foo(NULL); // this, most probably, will not call foo(char)
}
Note that the gcc compiler used at Coliru defines NULL
as 0L
, which for this example means that foo(NULL)
resolves to foo(long)
rather than to foo(void*)
. This answer discusses that aspect in detail.
Solution 2 - C++
Definition of macro NULL in C++
Leon is correct that when there are several overloads for the same function, \0
would prefer the one that takes parameter of type char
. However, it is important to notice that on a typical compiler, NULL
would prefer the overload that takes parameter of type int
, not of type void*
!
What probably causes this confusion is that C language allows defining NULL
as (void*)0
. C++ standard explicitly states (draft N3936, page 444):
> Possible definitions [of macro NULL
] include 0
and 0L
, but not (void*)0
.
This restriction is necessary, because e.g. char *p = (void*)0
is valid C but invalid C++, whereas char *p = 0
is valid in both.
In C++11 and later, you should use nullptr
, if you need a null constant that behaves as pointer.
How Leon's suggestion works in practice
This code defines several overloads of a single function. Each overload outputs the type of the parameter:
#include <iostream>
void f(int) {
std::cout << "int" << std::endl;
}
void f(long) {
std::cout << "long" << std::endl;
}
void f(char) {
std::cout << "char" << std::endl;
}
void f(void*) {
std::cout << "void*" << std::endl;
}
int main() {
f(0);
f(NULL);
f('\0');
f(nullptr);
}
On Ideone this outputs
int
int
char
void*
Therefore I would claim that the problem with overloads is not an actual application but a pathological case. The NULL
constant will behave wrong anyway, and should be replaced with nullptr
in C++11.
What if NULL is not zero?
Another pathological case is suggested by Andrew Keeton at another question:
> Note that what is a null pointer in the C language. It does not matter on the underlying architecture. If the underlying architecture has a null pointer value defined as address 0xDEADBEEF, then it is up to the compiler to sort this mess out. > > As such, even on this funny architecture, the following ways are still valid ways to check for a null pointer: > > if (!pointer) > if (pointer == NULL) > if (pointer == 0) > > The following are INVALID ways to check for a null pointer: > > #define MYNULL (void *) 0xDEADBEEF > if (pointer == MYNULL) > if (pointer == 0xDEADBEEF) > > as these are seen by a compiler as normal comparisons.
Summary
All in all, I would say that the differences are mostly stylistic. If you have a function that takes int
and overload that takes char
, and they function differently, you will notice difference when you call them with \0
and NULL
constants. But as soon as you place those constants in variables, the difference disappears, because the function that is called is deducted from the type of the variable.
Using correct constants makes the code more maintainable, and conveys meaning better. You should use 0
when you mean a number, \0
when you mean a character, and nullptr
when you mean a pointer. Matthieu M. points out in comments, that GCC had a bug, in which a char*
was compared to \0
, whereas the intention was to dereference the pointer and compare a char
to \0
. Such errors are easier to detect, if proper style is used thorough the codebase.
To answer your question, there is not really an actual use case that would prevent you from using \0
and NULL
interchangeably. Just stylistic reasons and some edge cases.
Solution 3 - C++
Please don't do this. It is an anti-pattern, and it is actually wrong. NULL is for NULL pointers, '\0'
is the null-character. They are logically different things.
I don't think I have ever seen this:
int* pVal='\0';
But this is fairly common:
char a=NULL;
But it is not good form. It makes the code less portable, and in my opinion less readable. It is also likely to cause issues in mixed C/C++ environments.
It relies on assumptions regarding how any particular implementation defines NULL. For instance, some implementations use a simple
#define NULL 0
Others might use:
#define NULL ((void*) 0)
And I have seen others defining as an integer, and all sorts of odd treatment.
NULL
should, in my opinion be used solely to indicate an invalid address. If you want a null-character, use '\0'
. Or define this as NULLCHR
. But that isn't as clean.
This will make your code more portable- you won't start getting warnings regarding types etc if you change compiler / environment /compiler settings. This might be more important in a C or mixed C/C++ environment.
An example of warnings that might arise: Consider this code:
#define NULL 0
char str[8];
str[0]=NULL;
This is equivalent to:
#define NULL 0
char str[8];
str[0]=0;
And we are assigning an integer value to a char. This may cause a compiler warning, and if there are enough occurrences of this, pretty soon you can't see any important warnings. And for me, this is the real problem. Having warnings in the code has two side effects:
- Given enough warnings, you don't spot new ones.
- It gives the signal that warnings are acceptable.
In both cases, actual bugs can then slip through, that would be caught by the compiler had we bothered to read the warnings (or turn on -Werror )
Solution 4 - C++
Yes, they may exhibit different behaviour while doing resolution of overloaded functions.
func('\0')
invokes func(char)
,
while
func(NULL)
invokes func(integer_type)
.
You can remove the confusion by using nullptr, which is always a pointer type, displays no ambiguity while assigning/comparing value or function overloading resolution.
char a = nullptr; //error : cannot convert 'std::nullptr_t' to 'char' in initialization
int x = nullptr; //error : nullptr is a pointer not an integer
Note that, it is still compatible with NULL:
int *p=nullptr;
if (p==NULL) //evaluates to true
Excerpt from C++ Programming Stroustrup 4th Edition book:
> In older code, 0 or NULL is typically used instead of nullptr > (§7.2.2). However, using nullptr eliminates potential confusion > between integers (such as 0 or NULL) and pointers (such as nullptr).
Solution 5 - C++
Computer programs have two types of readers.
The first type are computer programs, like the compiler.
The second type are humans, like yourself and your coworkers.
Programs are generally fine with getting one type of zero in place of another. There are exceptions, as the other answers have pointed out, but that is not really important.
What is important is that you are messing with the human readers.
Human readers are very context sensitive. By using the wrong zero, you are lying to you human readers. They are going to curse you.
A human that is lied to can overlook bugs more easily.
A human that is lied to can see "bugs" that aren't there. When "fixing" these phanthom bugs, they introduce real bugs.
Don't lie to your humans. One of the humans you are lying to is your future self. You are going to curse you too.
Solution 6 - C++
Excerpts from C++14 draft N3936:
> ###18.2 Types [support.types]
3 The macro NULL
is an implementation-defined C++ null pointer constant in this International Standard (4.10).
> ###4.10 Pointer conversions [conv.ptr]
1 A null pointer constant is an integer literal (2.14.2) with value zero or a prvalue of type std::nullptr_t
.
A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of object pointer or function pointer type.
Thus, NULL
can be any integer literal with value zero, or a value of type std::nullptr_t
like nullptr
, while '\0'
is always the zero narrow-character literal.
So, not in general interchangeable, even though in a pointer-context you cannot see any but stylistic difference.
An example would be:
#include <iostream>
#include <typeinfo>
int main() {
std::cout << typeid('\0').name << '\n'
<< typeid(NULL).name << '\n'
<< typeid(nullptr).name << '\n';
}
Solution 7 - C++
Let's define what's NULL in C/C++.
According to the C/C++ reference NULL is defined as a macro that expands to a null pointer constant. Next we can read that a null pointer constant can be converted to any pointer type (or pointer-to-member type), which acquires a null pointer value. This is a special value that indicates that the pointer is not pointing to any object.
Definition referring to C:
> A null-pointer constant is an integral constant expression that > evaluates to zero (like 0 or 0L), or the cast of such value to type > void* (like (void*)0).
Definition referring to C++98:
> A null-pointer constant is an integral constant expression that evaluates to zero (such as 0 or 0L).
Definition referring to C++11:
> A null-pointer constant is either an integral constant expression that evaluates to zero (such as 0 or 0L), or a value of type nullptr_t (such as nullptr).
Overloading methods example.
Let's assume that we have following methods:
class Test {
public:
method1(char arg0);
method1(int arg0);
method1(void* arg0);
method1(bool arg0);
}
Calling method1 with argument NULL
or nullptr
should call method1(void* arg0);
. However if we call method1 with argument '\0'
or 0
should execute method1(char arg0);
and method1(int arg0);
.