nullptr、NULL、null和0
C语言和C++对大小写是敏感的,也就是说null和NULL是区别对待的。NULL代表空地址,null只是一个符号,null可以是自己定义的一个变量。
NUll是一个宏定义 #define NULL 0,容易产生宏常常产生的错误。
nullptr是C++11中才引入的一个字面值常量,可以被转换成任意其他指针,其用法和直接用字面值0是一样的。
以下不完全转载至http://www.kiford.com/a/WZ012116VDNEF3BD?utm_source=tuicool&utm_medium=referral
在强调一下,NULL 在C++中的定义仅仅是 0,仅此而已。(切记,NULL 是宏,以下错误都是宏引起的)
当然,这看起来仅仅是语法问题啦。那么使用 nullptr 和 NULL 究竟有没有差别呢?当然有! 使用 nullptr 可以帮助我们避免各式各样的错误。
还是用实例来说明吧。
假设,有两个被重载的函数:
void Foo(int x, int y, const char *name);
void Foo(int x, int y, int ResourceID);
我们可能像下面这样来调用:
1.Foo(1, 2, NULL);
有些程序猿认为这样写调用的是第一个函数。结果却不是这样的,因为 NULL 仅仅就是 0,是int类型的 0,所以第2个函数会被调用,而不是第一个函数。
然而,如果使用的是 nullptr,那么第一个函数将被调用,就不会产生这种错误了。
另一个通常的使用 NULL 方式如下代码所示:
if (unknownError)
throw NULL;
依我之见,抛出一个指针异常这件事儿就是令人怀疑的。不过有时一些人会这么做。显然,开发者需要编写这样的代码。好吧,讨论这样做的好与坏超出了本文的范畴。
重点是我们想在产生未知错误时抛出一个异常,并且要把一个空指针"送到"外部世界。但实际上送出去的不是指针,而是一个int类型。结果就是异常并不会像我们认为的那样被捕获 。
throw nullptr; 可以让我们避免这种错误。但,好吧,这并不意味着我完全接受这种(抛出指针异常)的代码。
在一些情况下,使用 nullptr 是会产生编译错误的。
例如一些WinApi函数返回 HRESULT 类型的参数。HRESULT 类型和指针本没什么关系,然而如下所示的这种渣渣代码却有可能被写出来:
if (WinApiFoo(a, b, c) != NULL)
编译没有问题,因为 NULL 是int类型的0,HRESULT 是long型,比较类型int和long是可以的。如果使用 nullptr 呢,如下所示的代码就编译不过去啦:
if (WinApiFoo (a, b, c)! = nullptr)
如此,这个错误便能够被立刻被发现并改正啦。
你肯定有这种想法,这种例子是有很多,但这都是你自己臆想的啊,这可不能说服大家。有没有实际的例子呢?当然有,这就有一个,只不过不是那种短小精悍的例子。
这段代码来自 MTASA。
有这么一个 RtlFillMemory() ,它可能是一个函数或者宏,这都不重要。它和 memset() 有点像,只不过第二个和第三个参数是颠倒的。他可能是这样声明的:
#define RtlFillMemory(Destination,Length,Fill) \
memset((Destination),(Fill),(Length))
还有一个 FillMemory(),就是把 RtlFillMemory() 重定义了一下:
#define FillMemory RtlFillMemory
好吧,挺长挺乱的,但这确实是实际中的一个例子。
FillMemory 是这么被使用的:
LPCTSTR __stdcall GetFaultReason ( EXCEPTION_POINTERS * pExPtrs ){
....
PIMAGEHLP_SYMBOL pSym = (PIMAGEHLP_SYMBOL)&g_stSymbol ;
FillMemory ( pSym , NULL , SYM_BUFF_SIZE ) ;
....
}
这段代码bug不止一个。明显的就是第2个参数和第3个参数用反了。这就是分析器报了两个警告的原因 V575:
V575 The 'memset' function processes value '512'. Inspect the second argument. crashhandler.cpp 499 (memset 处理的值是'512',检查一下第2个参数)
V575 The 'memset' function processes '0' elements. Inspect the third argument. crashhandler.cpp 499(memset 处理了0个元素,检查一下第1个参数)
但错误可不止这一处:NULL 用在这压根就不合适。memset() 函数是按字节填充的,所以使用 NULL 值来填充的想法就是错(译者注:虽然 memset 的第2个参数value是int型,但实现时会转化成 unsigned char 来填充内存,参看memset文档)。 来看看正确的代码:
FillMemory(pSym, SYM_BUFF_SIZE, 0);
或者这样:
ZeroMemory(pSym, SYM_BUFF_SIZE);
但这也不是重点,重点是这种渣渣代码居然能 编~译~成~功~。可是如果程序猿们习惯使用的是 nullptr 而不是 NULL,那么代码就会写成这样:
FillMemory(pSym, nullptr, SYM_BUFF_SIZE);
这样的话编译器会抛出一个编译错误,猿们就能意识到他们的错了,就会多多注意他们的代码了。
另外,我明白这种情况下不应该责备 NULL 本身,这只能怪代码编译时没有抛出关于 NULL 的警告,我们的代码应该尽可能让编译器来发现错误。