'\ 0'和NULL可以互换使用吗?

cwe*_*est 55 c++ null

NULL通常在指针的上下文中使用,并且通过多个标准库(例如<iostream>)中的宏来定义为整数0.'\0'是空字符,是8位零.顺便提一下,8位零等于整数0.

在某些情况下,虽然它被认为是可怕的风格,但这两者可以互换:

int *p='\0';
if (p==NULL) //evaluates to true
    cout << "equal\n";
Run Code Online (Sandbox Code Playgroud)

要么

char a=NULL;
char b='\0';
if (a==b) //evaluates to true
    cout << "equal again\n";
Run Code Online (Sandbox Code Playgroud)

关于SO已经有很多类似的问题; 例如,这个问题的最佳答案(NULL,'\ 0'和0之间的区别是什么)说"它们实际上不是一回事".

任何人都可以提供一个例子NULL\0不能互换(优选实际应用,而不是一个病理情况)?

Leo*_*eon 52

任何人都可以提供一个例子,NULL和\ 0不能互换吗?

NULL和之间的差异'\0'可能会影响重载分辨率.

示例(在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)
}
Run Code Online (Sandbox Code Playgroud)

请注意,Coliru使用的gcc编译器定义NULL0L,对于此示例,这意味着foo(NULL)解析为foo(long)而不是foo(void*).这个答案详细讨论了这个方面.

  • 在C++中,`foo(NULL)`将始终调用`foo(int)`或`foo(long)`,因为C++标准不允许将`NULL`定义为`(void*)0`.有关详细信息,请参阅我的答案:http://stackoverflow.com/a/40396865/5489178 (8认同)
  • 也可以调用foo(nullptr)来完成 (7认同)
  • @hvd这是一个很好的例子.它清楚地显示了`NULL`与`'\ 0'`不可互换的位置.不仅如此,作为一个樱桃蛋糕,它揭示了一个相同的问题,`NULL`不能与`nullptr`互换.当然,问题根本没有描述,但只要答案不建议使用"NULL",我认为这很好.特别是因为这个问题对于问题来说有点偏僻. (7认同)
  • 我认为这是一个不好的例子,因为`foo(NULL)`在当前实现上更可能调用`foo(int)`而不是`foo(void*)`.是的,那仍然不是`foo(char)`,但是`foo(void*)`更可能是程序员想要的. (4认同)

vll*_*vll 36

C++中宏NULL的定义

Leon是正确的,当同一个函数有多个重载时,\0更喜欢带参数类型的那个char.但是,重要的是要注意在典型的编译器上,NULL更喜欢带有类型参数的重载int,而不是类型void*!

可能导致这种混淆的是C语言允许定义NULL(void*)0.C++标准明确声明(草案N3936,第444页):

[宏可能的定义NULL]包括00L,但不会(void*)0.

这种限制是必要的,因为例如char *p = (void*)0有效的C但无效的C++,而char *p = 0两者都有效.

在C++ 11及更高版本中,nullptr如果需要一个行为指针的null常量,则应该使用它.

莱昂的建议如何在实践中发挥作用

此代码定义了单个函数的多个重载.每个重载都输出参数的类型:

#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);
}
Run Code Online (Sandbox Code Playgroud)

在Ideone上输出

int
int
char
void*
Run Code Online (Sandbox Code Playgroud)

因此,我认为重载问题不是实际应用,而是病态案例.该NULL常数的行为错了,无论如何,并且应该被替换nullptr在C++ 11.

如果NULL不为零怎么办?

另一个问题是Andrew Keeton提出的另一个病理案例:

请注意,C语言中的空指针是什么.它与底层架构无关.如果底层架构的空指针值定义为地址0xDEADBEEF,那么由编译器来排除这个混乱局面.

因此,即使在这个有趣的架构上,以下方法仍然是检查空指针的有效方法:

if (!pointer)
if (pointer == NULL)
if (pointer == 0)
Run Code Online (Sandbox Code Playgroud)

以下是检查空指针的INVALID方法:

#define MYNULL (void *) 0xDEADBEEF
if (pointer == MYNULL)
if (pointer == 0xDEADBEEF)
Run Code Online (Sandbox Code Playgroud)

因为这些被编译器视为正常比较.

摘要

总而言之,我会说差异主要是风格.如果你有一个接受int和重载的函数char,并且它们的功能不同,当你用它们\0NULL常量调用时,你会注意到它们之间的区别.但是只要将这些常量放在变量中,差异就会消失,因为调用的函数是从变量的类型中扣除的.

使用正确的常量可以使代码更易于维护,并更好地传达意义.0当你的意思是一个数字时,你应该使用,\0当你的意思是一个字符,nullptr当你指的是一个指针时.马修M.指出了意见,那GCC有一个bug,在其中char*进行比较\0,而目的是取消引用指针和比较char\0.如果在代码库中使用了合适的样式,则更容易检测到这些错误.

要回答您的问题,实际上并没有一个实际的用例会阻止您使用\0NULL互换.只是风格的原因和一些边缘情况.

  • @KyleStrand`char*p =(void*)0;`是有效的C但无效的C++.`char*p = 0;`也是有效的C++ (5认同)
  • 为什么`NULL`特别需要*不*为`(void*)`? (4认同)

mjs*_*mjs 8

请不要这样做.这是一种反模式,实际上是错误的.NULL用于NULL指针,'\0'是空字符.它们在逻辑上是不同的东西.

我不认为我见过这个:

int* pVal='\0';
Run Code Online (Sandbox Code Playgroud)

但这很常见:

char a=NULL;
Run Code Online (Sandbox Code Playgroud)

但这不是好形式.它使代码不那么便携,而且在我看来不太可读.它也可能在混合C/C++环境中引起问题.

它依赖于有关任何特定实现如何定义NULL的假设.例如,一些实现使用简单的

#define NULL 0
Run Code Online (Sandbox Code Playgroud)

其他人可能使用:

#define NULL ((void*) 0)
Run Code Online (Sandbox Code Playgroud)

我已经看到其他人定义为整数,以及各种奇怪的处理.

NULL在我看来,应仅用于表示无效地址.如果你想要一个空字符,请使用'\0'.或者将其定义为NULLCHR.但那不是那么干净.

这将使您的代码更具可移植性 - 如果您更改编译器/环境/编译器设置,则不会开始收到有关类型等的警告.这在C或混合C/C++环境中可能更为重要.

可能出现警告的示例:请考虑以下代码:

#define NULL 0
char str[8];
str[0]=NULL;
Run Code Online (Sandbox Code Playgroud)

这相当于:

#define NULL 0
char str[8];
str[0]=0;
Run Code Online (Sandbox Code Playgroud)

我们正在为char分配一个整数值.这可能会导致编译器警告,如果有足够的事件发生,很快您就看不到任何重要的警告.对我来说,这是真正的问题.代码中有警告有两个副作用:

  1. 如果有足够的警告,你就不会发现新的警告.
  2. 它给出了警告可以接受的信号.

在这两种情况下,实际的错误都可以通过,如果我们打扰阅读警告(或打开-Werror),编译器会捕获这些错误

  • `#define NULL(void*)0`仅在C中有效(如果添加了括号),而不是在C++中.在C++ 03中,`char c = NULL;`是可移植但风格不好.C++ 11允许`#define NULL nullptr`,`char c = NULL;`允许失败. (5认同)

Sau*_*ahu 8

是的,在解决重载功能时,它们可能表现出不同的行为.

func('\0')调用func(char),

func(NULL)调用func(integer_type).


您可以使用nullptr消除混淆,nullptr始终是指针类型,在分配/比较值或函数重载分辨率时不显示歧义.

char a = nullptr; //error : cannot convert 'std::nullptr_t' to 'char' in initialization
int x = nullptr;  //error : nullptr is a pointer not an integer
Run Code Online (Sandbox Code Playgroud)

请注意,它仍然与NULL兼容:

int *p=nullptr;
if (p==NULL) //evaluates to true
Run Code Online (Sandbox Code Playgroud)

摘自C++ Programming Stroustrup第4版书:

在较旧的代码中,通常使用0或NULL而不是nullptr(第7.2.2节).但是,使用nullptr消除了整数(例如0或NULL)和指针(例如nullptr)之间的潜在混淆.



Sti*_*mer 7

计算机程序有两种类型的读者.

第一种类型是计算机程序,如编译器.

第二种类型是人类,就像你自己和你的同事一样.

程序通常很好,只有一种类型的零代替另一种类型.正如其他答案所指出的那样,也有例外,但这并不重要.

重要的是你正在弄乱人类的读者.

人类读者非常敏感.通过使用错误的零,你是骗人的读者.他们会诅咒你.

被骗的人可以更容易地忽视错误.
被骗的人可以看到不存在的"虫子".当"修复"这些phanthom错误时,它们会引入真正的错误.

不要欺骗你的人类.你所撒谎的人之一就是你未来的自我.你也会诅咒你.