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

Can '' and NULL be used interchangeably?

本文关键字:互换 NULL      更新时间:2023-10-16

NULL通常用于指针上下文中,并通过多个标准库(如<iostream>)中的宏定义为整数0''为空字符,为8位零。顺便说一下,8位零相当于整数0

在某些情况下,尽管它被认为是糟糕的样式,但这两者可以互换:

int *p='';
if (p==NULL) //evaluates to true
    cout << "equaln";

char a=NULL;
char b='';
if (a==b) //evaluates to true
    cout << "equal againn";

单就SO而言,已经有很多类似的问题了;例如,这个问题的最佳答案(NULL, ''0)说:"它们其实不是一回事。"

谁能提供一个NULL不能互换的例子(最好是实际应用而不是病理案例)?

谁能提供一个例子,NULL和不能互换?

NULL''之间的差异可能会影响过载解析。

示例(在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(''); // this will definitely call foo(char)
    foo(NULL); // this, most probably, will not call foo(char)
}

注意,在Coliru中使用的gcc编译器将NULL定义为0L,这在本例中意味着foo(NULL)解析为foo(long)而不是foo(void*)。本回答详细讨论了这方面。

c++中宏NULL的定义

Leon是正确的,当同一函数有多个重载时,更倾向于接受char类型参数的函数。然而,重要的是要注意,在典型的编译器上,NULL更倾向于接受int类型参数的重载,而不是接受void*类型参数的重载!

可能导致这种混淆的原因是C语言允许将NULL定义为(void*)0。c++标准明确规定(草案N3936, page 444):

NULL的可能定义包括00L,但不包括(void*)0

这个限制是必要的,因为例如char *p = (void*)0在C语言中有效,但在c++语言中无效,而 char *p = 0在两种语言中都有效。

在c++ 11及以后的版本中,如果你需要一个作为指针的空常量,你应该使用nullptr

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

这段代码定义了单个函数的多个重载。每次重载输出参数的类型:

#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('');
    f(nullptr);
}

在Ideone上输出

int
int
char
void*

因此,我认为重载的问题不是一个实际的应用程序,而是一个病态的情况。无论如何,NULL常量的行为是错误的,应该在c++ 11中用nullptr替换。

如果NULL不为零怎么办?

另一个病理案例由Andrew Keeton在另一个问题中提出:

注意什么是C语言中的空指针。这与底层架构无关。如果底层架构有一个定义为地址0xDEADBEEF的空指针值,则由编译器来对这个混乱进行排序。

因此,即使在这种奇怪的架构上,以下方法仍然是检查空指针的有效方法:
if (!pointer)
if (pointer == NULL)
if (pointer == 0)

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

#define MYNULL (void *) 0xDEADBEEF
if (pointer == MYNULL)
if (pointer == 0xDEADBEEF)

,因为这些被编译器视为正常的比较。

<标题> 总结总而言之,我想说差异主要是文体上的。如果你有一个接受int的函数和一个接受char的函数,它们的作用是不同的,当你用NULL常量调用它们时,你会注意到它们的不同。但是,一旦将这些常量放入变量中,差异就消失了,因为调用的函数会从变量的类型中扣除。

使用正确的常量使代码更易于维护,并更好地传达意义。表示数字时应使用0,表示字符时应使用,表示指针时应使用nullptr。Matthieu M.在评论中指出,GCC有一个bug,其中char*进行比较,而意图是取消对指针的引用并将char进行比较。如果在整个代码库中使用适当的样式,这样的错误更容易检测。

回答你的问题,实际上并没有一个实际的用例可以阻止你互换使用NULL。只是文体原因和一些极端情况。

请不要这样做。这是一种反模式,实际上是错误的。NULL表示NULL指针,''是空字符。它们在逻辑上是不同的。

我想我从来没有见过这个:

int* pVal='';

但这是相当常见的:

char a=NULL;

但这不是好的形式。它使代码的可移植性降低,而且在我看来可读性降低。在C/c++混合环境中,这也可能导致问题。

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

#define NULL 0

其他人可能会使用:

#define NULL ((void*) 0)

我还见过其他人将其定义为整数,以及各种奇数处理。

在我看来,

NULL应该只用于表示无效的地址。如果需要一个空字符,请使用''。或者定义为NULLCHR。但那就没那么干净了。

这将使你的代码更具可移植性——如果你改变编译器/环境/编译器设置,你不会开始收到关于类型等的警告。这在C或C/c++混合环境中可能更重要。

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

#define NULL 0
char str[8];
str[0]=NULL;

这相当于:

#define NULL 0
char str[8];
str[0]=0;

我们将整数值赋给char类型。这可能会导致编译器警告,如果这种情况出现得足够多,很快您就看不到任何重要的警告。对我来说,这才是真正的问题。在代码中使用警告有两个副作用:

  1. 给足够的警告,你不会发现新的。
  2. 给出警告可接受的信号。

在这两种情况下,实际的错误可能会被遗漏,如果我们费心去阅读警告(或打开-Werror)

是的,它们在解析重载函数时可能会表现出不同的行为。

func('')调用func(char)

,

func(NULL)调用func(integer_type)


您可以通过使用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

注意,它仍然与NULL:

兼容。
int *p=nullptr;
if (p==NULL) //evaluates to true

节选自c++ Programming Stroustrup第4版书:

在旧代码中,通常使用0或NULL来代替nullptr(§7.2.2)。但是,使用nullptr可以消除潜在的混淆在整数(如0或NULL)和指针(如nullptr)之间。


计算机程序有两种类型的读取器。

第一类是计算机程序,如编译器。

第二类是人类,比如你自己和你的同事。

程序通常可以用一种类型的零代替另一种类型的零。正如其他答案所指出的,也有例外,但这并不重要。

重要的是你是在干扰人类读者。

人类读者非常上下文敏感。通过使用错误的零,你是在对你的人类读者撒谎。他们会诅咒你的。

一个被欺骗的人更容易忽略错误。
一个被骗的人可以看到不存在的"bug"。当"修复"这些虚幻的bug时,它们会引入真正的bug。

不要对你的人类撒谎。你在对未来的自己撒谎。你也会诅咒你自己的。

c++ 14 draft N3936节选:

18.2 Types [support.types]

NULL是本国际标准(4.10)中实现定义的c++空指针常量。

4.10指针转换[convr .ptr]

空指针常量是一个值为0或类型为std::nullptr_t的整型字面值(2.14.2)。
空指针常量可以转换为指针类型;结果是该类型的空指针值,该值与其他所有对象指针或函数指针类型的值不同。

因此,NULL可以是任何值为0的整数字面值,也可以是std::nullptr_t类型的值,如nullptr,而''始终是零窄字符字面值。

所以,通常不能互换,即使在指针上下文中,你也看不到任何风格上的差异。

一个例子是:

#include <iostream>
#include <typeinfo>
int main() {
    std::cout << typeid('').name << 'n'
        << typeid(NULL).name << 'n'
        << typeid(nullptr).name << 'n';
}

让我们在C/c++中定义什么是NULL。

根据C/c++参考,NULL被定义为一个宏,扩展为一个空指针常量。接下来我们可以读到,空指针常量可以转换为获取空指针值的任何指针类型(或指针到成员类型)。这是一个特殊值,表示指针不指向任何对象。

关于C的定义:

空指针常量是一个整型常量表达式计算结果为0(如0或0L),或者将这样的值强制转换为typeVoid *(类似于(Void *)0).

引用c++ 98的定义:

空指针常量是求值为0(如0或0L)的整型常量表达式。

引用c++ 11的定义:

空指针常量要么是求值为零的整型常量表达式(如0或0L),要么是类型为nullptr_t的值(如nullptr)。

重载方法示例。

假设我们有以下方法:

class Test {
public:
    method1(char arg0);
    method1(int arg0);
    method1(void* arg0);
    method1(bool arg0);
}

调用参数为NULLnullptr的method1应该调用method1(void* arg0);。然而,如果我们用参数''0调用method1,应该执行method1(char arg0);method1(int arg0);