C/C++中的NULL、0和nullptr的区别

C/C++ 中,为了防止野指针(指针指向的位置是不可知的),我们常常需要将指针赋值为空指针。在 C 中我们用 NULL 来表示空指针,而在 C++ 中则有 NULLnullptr 两种空指针,并且其 NULL 的定义也和 C 语言中的完全不同。我们就在这篇文章中仔细研究一下。

NULL

C 语言中,我们使用 NULL 来表示空指针。他是一个宏定义,其定义是

1
#define NULL ((void *)0)
也就是说 NULL 其实是一个 void* 类型的指针。并且在 C 语言中,任何类型的指针都可以(隐式地)转换为 void * 类型,反过来也成立。但是在 C++ 中则不存在这种隐式的转换。比如在 C 语言中,
1
int *i = (void *)0;
上述的写法是完全合法的,但是使用 C++ 编译器则会报错。在 C++NULL 被定义为整数 0 :
1
2
3
4
5
6
7
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

0

因此在 C++ 中,定义一个空指针:

1
int *p = NULL;
实际上就是将指针 p 赋值为零。在 C++ 中为0的指针都会被认为是空指针。 但是这样的话,就会造成一些歧义:0 即可以表示整数常量,又可以表示空指针常量。那么在下面的经典例子中:
1
2
3
4
5
void foo(int) {}    // #1
void foo(char*) {} // #2
int main() {
foo(NULL);
}
foo(NULL) 到底应该调用哪个函数?既然 NULL 是指针,它就应该调用 #2 ,但实际上 NULL 又被定义为整数0,所以会调用 #1 .

为了解决这种歧义性,于是又引入了一种新的空指针常量 nullptr .

nullptr

C++11 中引入了一个新的关键字 nullptr,其定义为

1
typedef decltype(nullptr) nullptr_t;
这里 decltype 也是 C++11 中新引入的关键字,用于自动类型推导。

  • nullptr_t 是一种数据类型,而 nullptr 是一个常数,是该类型的一个实例。
  • 因此可以通过 nullptr_t 类型创建另一个新的实例。但所有定义为 nullptr_t 类型的数据都是等价的,行为也是完全一致的。
  • nullptr 是一个编译期常量,其类型为 nullptr_t,它既不是整型类型,也不是指针类型。

除此之外,nullptr 还有一些其他的特性:

  • std::nullptr_t 类型并不是指针类型,但可以隐式转换成任意一个指针类型。
  • nullptr_t 类型数据不能转换为非指针类型,即使使用reinterpret_cast<nullptr_t>() 的方法也不行。
  • nullptr_t 类型数据不适用于算术运算表达式。
  • nullptr_t 类型数据可以用于关系运算表达式,但仅能与 nullptr_t 类型数据或者指针类型数据进行比较,当且仅当关系运算符为 ==,<=,>= 等时返回true。

这些特性可以在代码中得到验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//nullptr可以隐式转换为char*
char *cp = nullptr;

//nullptr不能转换为非指针类型,任何类型也不能转换为nullptr_t
int i1 = nullptr; //error
int i2 = reinterpret_cast<nullptr_t>(nullptr); //error

//nullptr与nullptr_t类型变量可以作比较,当使用==,<=,>=符号作比较时才返回true
nullptr_t nptr;
if (nptr == nullptr) //nullptr_t nptr == nullptr
if (nptr < nullptr) //nullptr_t nptr >= nullptr

//不能转换为整型或者bool类型
if (0 == nullptr); //error
if (nullptr); //error

//不能进行算术运算
nullptr += 1; //error
nullptr * 5; //error

void* 区别

  • C++11 标准中,nullptr 类型数据所占用的内存空间,与 void* 所占内存空间大小相同,即 sizeof(nullptr_t) == sizeof(void*).
  • 语法内涵不同,nullptr 是一个编译期常量,而 (void*)0 只是一个强制转换表达式,其返回的也是一个 void* 指针类型。
  • C++ 语言中,nullptr 到任何指针的转换是隐式的,而 (void*)0 则必须经过类型转换后才能使用。

nullptr 可能的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct nullptr_t
{
void operator&() const = delete; // Can't take address of nullptr

template <class T>
inline operator T*() const
{
return 0;
}

template <class C, class T>
inline operator T C::*() const
{
return 0;
}
};
nullptr_t nullptr;

源码这部分需要继续学习~


C/C++中的NULL、0和nullptr的区别
https://quantum-cyborg.github.io/2017/11/10/CS/cpp/NULL 0 and nullptr in C++/
作者
碳基机器
发布于
2017年11月10日
许可协议