在C++中,指针使用NULL还是0(零)?

205

在C++早期,它是建立在C的基础之上的。你不能使用NULL,因为它被定义为(void*)0。你不能将NULL赋值给除void*外的任何指针,这使其变得有点无用。在那个时代,人们普遍使用0(零)表示空指针。

直到今天,我仍然使用零作为空指针,但我周围的人坚持使用NULL。我个人认为给一个已经存在的值(NULL)起一个名称没有任何好处,而且因为我也想将指针测试为真值:

if (p && !q)
  do_something();

在这种情况下,使用零更有意义(例如如果您使用NULL,则不能逻辑上使用p&& !q - 您需要明确与NULL进行比较,除非您假设NULL为零,在这种情况下为什么使用NULL)。

是否有任何客观理由更喜欢零而不是NULL(或反之亦然),还是所有都只是个人喜好?

编辑:我应该补充说明(并最初打算说)使用RAII和异常后,我很少使用零/NULL指针,但有时您仍然需要它们。


9
等等,不是无效指针必须在内部为零时才能被评估为false吗? - Mooing Duck
5
“https://dev59.com/jWkw5IYBdhLWcg3wbqES#tqGdEYcBWogLw_1bqZj5” 和 “http://c-faq.com/null/ptrtest.html” 确认了在 C 语言中,null 值通常被定义为零值。 - Mooing Duck
21个回答

201
这是Stroustrup对此的看法:C++风格和技巧FAQ 在C++中,NULL的定义为0,因此只有美学上的区别。我喜欢避免使用宏,所以我使用0。 NULL的另一个问题是人们有时会错误地认为它与0不同,或者不是整数。 在早期标准之前的代码中,NULL有时被定义为不适当的内容,因此必须避免使用。但这种情况现在已经不太常见了。
如果你必须给空指针取个名字,那就叫做nullptr吧;这就是C++11中所称呼的。然后,nullptr将成为关键字。
话虽如此,不要为小事烦恼。

9
在C++0x开始研发新的空类型之前,Bjarne写下了这段话。当这种类型在平台上可用时,将使用NULL表示它,我认为你会看到对此一般共识的C语言改变。 - Richard Corden

127

有几个论点(其中一个是相对较新的),我认为与Bjarne的立场相矛盾。

  1. 意图的文档化

    使用NULL可以进行搜索,同时也突出显示开发人员想要使用NULL指针,无论编译器是否将其解释为NULL

  2. 指针和'int'的重载相对较少

    每个人都引用的例子是:

     void foo(int*);
     void foo (int);
    
     void bar() {
       foo (NULL);  // Calls 'foo(int)'
     }
    

    然而,我认为上述问题的问题并不在于我们使用NULL作为空指针常量,而是我们有各种不同类型参数的foo()重载。该参数还必须是int类型,因为任何其他类型都会导致模棱两可的调用,从而生成一个有用的编译器警告。

  3. 分析工具可以帮助今天!

    即使在没有C++0x的情况下,今天也有可用的工具可以验证指针是否使用了NULL,以及整数类型是否使用了0

  4. C++ 11将拥有一个新的std::nullptr_t类型。

    这是桌子上最新的论点。对于C++0x正在积极解决0NULL的问题,并且您可以保证对于每个提供NULL的实现,它们将做的第一件事是:

  5.  #define NULL  nullptr
    

    对于那些使用NULL而不是0的人来说,这个变化将是一种类型安全的改进,几乎没有任何努力-如果有什么问题,它也可能会捕获一些他们使用NULL代替0的错误。 对于今天使用0的任何人...好吧,希望他们对正则表达式有很好的认识...


2
@Richard,为什么不做相反的呢?你可以使用Meyers nullptr_t,然后当0x变得可用时,移除#include并一直保持安全。 - Fernando N.
17
#define NULL nullptr 看起来存在一定风险。无论好坏,很多旧代码将 NULL 用于代替 0 的其他含义。例如,句柄通常实现为某种整数类型,将它们设置为 NULL 并不罕见。我甚至看到过将 NULL 用于将 char 设置为零终止符的滥用情况。 - Adrian McCarthy
8
如果代码默默地编译并具有不同的含义,那么我才会说这是危险的。我相当确定这种情况并不会发生,所以实际上所有错误使用 NULL 的情况都将被检测出来。 - Richard Corden
3
@RichardCorden说:嗯,这假设那些对NULL的其他使用实际上是不正确的。许多API长期以来一直将NULL与句柄一起使用,并且事实上,许多API文档中都有这种用法。突然打破它们并宣称他们做错了是不切实际的。 - Adrian McCarthy
1
@AdrianMcCarthy:这并不是非黑即白的问题。如果你正在使用这样的API,那么你需要快速将NULL更改为0。如果你没有使用这样的API,那么你可以利用使用错误的地方并进行修复。 - Richard Corden
显示剩余3条评论

47

使用 NULL。NULL 显示您的意图。它为 0 是一个不应该有影响的实现细节。


29
0 不是一个实现细节。 标准将 0 定义为表示空指针的任何位模式。 - Ferruccio
5
好的,我会尽力进行翻译。仿佛...!!老兄,C++是一种低级语言!使用0,这是一个众所周知的习惯用语。 - hasen
11
我理解这是标准的一部分。就代码阅读而言,这是实现细节。读者应该想到“空指针”,而不是“0,这里表示空指针,不能用于算术运算”。 - Andy Lester
5
+1. 同意安迪的观点。@Ferruccio,程序员想法的实现细节不等于编译器定义的实现规定 - user
如果您在一个简单的代码中使用NULL,而没有复杂的头文件,您将会发现"NULL未在此范围内定义"的错误。 - ArtificiallyIntelligence
如果出现“NULL is not defined in this scope”的情况,则只需#include <stddef.h> - chen3feng

47

我总是这样使用:

  • 指针使用NULL
  • 字符使用'\0'
  • 浮点数和双精度数使用0.0

虽然用0也可以,但我更喜欢这种方式来表达意图。不过,我对此并不太纠结。


27
建议在使用浮点数时,应该使用0.0F来避免隐式类型转换。 - EvilTeach

34

我早就停止使用NULL(以及大多数其他宏)而改用0。我这么做不仅是因为我想尽可能避免使用宏,而且因为在C和C++代码中,NULL似乎被过度使用。它似乎被用于需要0值的情况,而不仅仅是指针。

在新项目中,我会将以下内容放入项目头文件:

static const int nullptr = 0;

现在,当C++0x兼容的编译器到来时,我只需要删除那行代码即可。 这样做的一个好处是Visual Studio已经将nullptr识别为关键字并正确地进行了高亮显示。


4
使用NULL在长期内更具可移植性。'nullptr'在某些平台上可用,而在其他平台上不可用。在此处使用NULL将自动确保它仅在需要时出现,而您的解决方案需要在声明周围使用预处理器来实现这一点。 - Richard Corden
6
我不同意。在编译器跟上之前,它在短期内会变得不太可移植。但是长期而言,它会变得和以前一样可移植,甚至更易读一些。 - Ferruccio
4
你可以为你的非 C++0x 编译器始终 #define nullptr 为 NULL。 - Anteru
我同意 NULL 被过度使用了,有时还用来指代字符串中的零终止符!但我认为这并不足以完全避免使用它。 - Mark Ransom

23
    cerr << sizeof(0) << endl;
    cerr << sizeof(NULL) << endl;
    cerr << sizeof(void*) << endl;

    ============
    On a 64-bit gcc RHEL platform you get:
    4
    8
    8
    ================

故事的寓意是,处理指针时应使用NULL。

1)这可以表明您的意图(不要让我在代码中搜索变量以确定它是指针还是某种数值类型)。

2)在某些API调用中,它们会使用NULL指针来表示参数列表的结尾。在这种情况下,使用“0”而不是NULL可能会导致问题。在64位平台上,va_arg调用需要一个64位指针,但您只会传递32位整数。似乎您依赖于其他32位被置零?我见过某些编译器(例如Intel的icpc)并不如此慷慨-这导致了运行时错误。


1
NULL 可能不具备可移植性和安全性。仍有一些平台会 #define NULL 0 (根据 Stroustrup FAQ:我应该使用 NULL 还是 0? 中所引用的顶部问题及其搜索结果)。至少在早期的 C++ 中,0 在指针上下文中具有特殊的概念意义。您不应该具体考虑位。此外,请注意,在不同的整数上下文(例如 shortintlong long)中 "sizeof(0)" 将不同。我认为这个答案有点误导。 - FooF
作为一名C程序员,我个人在日常工作中经常思考这个问题:为什么人们想要使用 NULL 而不是 (char *)0(const char *)0(struct Boo *)0(void *)0 或者其他表达意图更加清晰的方式呢?(在我看来,这些方式有些过于繁琐。) - FooF
1
点赞。这是在msvc2013 C编译器中发生的。在64位情况下,将0转换为指针时不能保证为NULL指针。 - pengMiao
NULL在标准中有明确定义,因此它是绝对可移植的。但使用NULL更清晰,在升级到C++11后,您可以轻松地搜索和替换NULL为nullptr,但对于0,您该怎么办呢? - chen3feng

17

如果我记得正确,NULL 在我所使用的头文件中的定义是不同的。对于 C 语言,它被定义为 (void*)0,而对于 C++ 语言,则被定义为 0。代码大致如下:

#ifndef __cplusplus
#define NULL (void*)0
#else
#define NULL 0
#endif

个人而言,我仍然使用 NULL 值来表示空指针,这使得你明确地知道你正在使用一个指针,而不是一些整数类型。是的,在内部,NULL 值仍然是 0,但它不会被表示为这样。

此外,我不依赖于整数自动转换为布尔值,而是显式地进行比较。

例如,更喜欢使用:

if (pointer_value != NULL || integer_value == 0)

与其:

if (pointer_value || !integer_value)
不需要多言,这一切在C++11中都已得到解决,只需使用nullptr替换NULL即可,同时nullptr_tnullptr的类型。

16

我认为历史已经证明,那些支持使用0(零)的人错了(包括Bjarne Stroustrup)。支持0的论点大多是美学和“个人喜好”。

在创建C++11后,由于其新的nullptr类型,一些编译器开始抱怨(默认参数),因为0不是指针,而是传递给具有指针参数的函数。

如果代码使用NULL编写,则可以通过代码库进行简单的搜索和替换,以便将其更改为nullptr。如果您被困在使用0作为指针的代码中,那么更新它会更加繁琐。

如果您现在必须按照C++03标准编写新代码(不能使用nullptr),则确实应该使用NULL。这将使您未来更新变得更加容易。


Bjarne Stroustrup 偏好使用 0,只是因为他不喜欢宏,但也不想引入新的关键词。历史证明了他是错的。 - chen3feng

11
我曾经在一台机器上工作过,其中0是一个有效的地址,NULL被定义为一个特殊的八进制值。在那个机器上(0!= NULL),所以像下面这样的代码:
char *p;

...

if (p) { ... }

无法按照您的预期工作。您必须编写


if (p != NULL) { ... }

尽管我相信大多数编译器如今将NULL定义为0,但我仍然记得那些年学到的教训:NULL不一定等于0。


28
你没有使用符合标准的编译器。标准规定NULL应该是0,并且编译器应该将在指针上下文中的0转换为适当的真正NULL值以供架构使用。 - Evan Teran
17
是的,你是正确的。这是在80年代中期,在ANSI制定C标准之前发生的。那时并没有合规性的概念,编译器编写者可以随意解释这种语言。这就是为什么需要一个标准的原因。 - mxg
@EvanTeran 这并不适用于C语言。(void *)0必须与NULL_相等比较_,但实际上它不一定是0。有些人认为NULL应该是0xffffffff...0xf7ffffff...,因为0x00可能是一个有效的地址,但到目前为止,大多数实现使用NULL=0 - yyny
@yyny 你误解了。 "null constant" 和 "null value" 是有区别的。按照标准,"null constant" 的定义是 0,这是你在代码中编写的内容。然而,编译器可能会选择在生成的机器代码中发出不同的 "null constant" 值。换句话说,当你写 p = 0;(其中 p 是指针)时,编译器将把 0 视为 "null constant"。但是,在发出存储 "null" 的指令时,将存储机器特定的 "null value",它可能不是字面上的地址 0x0 - Evan Teran

11

我通常使用0。我不喜欢宏,也不能保证你正在使用的某个第三方头文件未重新定义NULL为一些奇怪的值。

你可以像Scott Meyers和其他人建议的那样使用nullptr对象,直到C++有nullptr关键字为止:

const // It is a const object...
class nullptr_t 
{
public:
    template<class T>
    operator T*() const // convertible to any type of null non-member pointer...
    { return 0; }

    template<class C, class T>
    operator T C::*() const   // or any type of null member pointer...
    { return 0; }

private:
    void operator&() const;  // Can't take address of nullptr

} nullptr = {};

请搜索"nullptr"以获取更多信息。


11
任何将NULL定义为除0(或如果作为C代码编译时,定义为(void*)0)以外的任何内容的第三方库都在自找麻烦,不应使用。 - Adam Rosenfield
3
你有没有真正看过重新定义 NULL 的图书馆?有吗?如果这样的图书馆存在,那么你会面临比重新定义 NULL 更大的问题,比如你正在使用一个愚蠢到重新定义 NULL 的图书馆。 - Andy Lester
1
十多年前,我依稀记得曾经不得不处理一些第三方头文件,可能是Orbix或ObjectStore,它们确实定义了NULL。在尝试让各种第三方头文件与windows.h配合工作的几天几夜浪费后,我想我对宏有一种病态的厌恶感。 - jon hanson
3
“不喜欢宏(macros)”对于类似对象的#define来说是一个奇怪的评价。也许你的意思是你不喜欢C预处理器(C-preprocessor)? - Andrew Prock
1
重新定义标准宏是未定义行为,如果我没记错的话。 - Ayxan Haqverdili
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接