我可以为0的值使用NULL作为替代吗?

81

我可以使用 NULL 指针来替代 0 的值吗?

这样做有什么问题吗?


比如说:

int i = NULL;
作为代替:
int i = 0;

作为实验,我编译了以下代码:

#include <stdio.h>

int main(void)
{
    int i = NULL;
    printf("%d",i);

    return 0;
}

输出:

0

实际上,它给了我这个警告,这个警告本身是完全正确的:

warning: initialization makes integer from pointer without a cast [-Wint-conversion] 

但结果仍然等价。


  • 我是否会涉及到“未定义行为”?
  • 可以这样使用NULL吗?
  • 在算术表达式中使用NULL作为数值有问题吗?
  • C++中对于这种情况的结果和行为是什么?

我已经阅读了NULL,'\0'和 0 之间的区别是什么的答案,但我没有从那里获得简明的信息,是否可以合法地使用NULL作为赋值和其他算术操作中的值。


评论不适合进行长时间的讨论;这个对话已被移至聊天室 - Samuel Liew
1
最好分别提出两个问题,一个关于C语言,一个关于C++。 - Konrad Rudolph
7个回答

85
不可以。使用NULL指针作为0值的替代品是不安全的,因为NULL是一个空指针常量,在C语言中可能具有int类型,但更通常具有void *类型,或者不能直接赋值给int(在C++11及以上版本)。这两种语言都允许将指针转换为整数,但它们不提供这样的转换隐式执行(尽管一些编译器提供了这个扩展)。此外,虽然将空指针转换为整数得到值0是很常见的,但标准并不保证这一点。如果你想要一个类型为int且值为0的常量,请写成0
  • 这样做会导致未定义行为吗?
,如果NULL在实现中扩展为一个具有void *类型或任何其他不能直接分配给int的值。在这种实现中,该赋值的行为未定义,标准没有定义其行为。
  • 使用NULL进行操作是否被允许?

这是一种糟糕的风格,在某些系统和情况下可能会出错。鉴于您似乎正在使用GCC,如果编译选项中包含-Werror,则会在您的示例中出现错误。

  • 在算术表达式中使用NULL作为数值是否有问题?

是的。它不能保证具有数值,如果您的意思是0,则应该写0,这不仅定义明确,而且更加简洁明了。

  • C++对此情况的结果如何?

C++的转换规则比C语言更严格,并且对NULL有不同的规则,但是实现中也可能提供扩展。同样,如果您的意思是0,则应该写0。


4
你应该指出,“which more typically has type void *”只适用于C语言。在C++中,void *不是合法类型(因为你不能将void *分配给任何其他指针类型)。实际上,在C++89和C++03中,NULL必须是int类型,但在后续版本中,它可以是(并且通常是)nullptr_t类型。 - Martin Bonner supports Monica
1
你也错了,将 void* 转换为 int 是未定义行为。这并不是未定义的行为,而是实现指定的行为。 - Martin Bonner supports Monica
@MartinBonnersupportsMonica,在C语言中,当指针被转换为整数时,转换结果确实是由具体实现决定的,但这不是我所讨论的。将指针赋值给整数类型的lvalue(未经显式转换)会导致未定义的行为。该语言在此处并未定义自动转换。 - John Bollinger
@MartinBonnersupportsMonica,我已经编辑过了,更加包容C++的考虑。无论如何,核心主题同样适用于两种语言:如果你想要一个整数0,那么请明确地将其写成相应类型的整数常量。 - John Bollinger

32

NULL 是一个空指针常量。在 C 中,它可以是一个值为 0 的整数常量表达式,或者是将这样的表达式转换为 void*,后者更为常见。这意味着你不能假设可以将 NULL 和零互换使用。例如,在下面的代码示例中:

char const* foo = "bar"; 
foo + 0;

NULL替换0在C程序中不一定是有效的,因为两个指针(尤其是不同类型的指针)之间的加法未定义。这将导致因约束违规而发出诊断信息。加法操作数无效


对于C++,情况有些不同。缺少从void*到其他对象类型的隐式转换意味着在C++代码中,NULL在历史上被定义为0。在C++03中,你可能可以做到。但自从C++11以来,它可以合法地被定义为关键字nullptr。现在再次产生错误,因为std::nullptr_t可能不会添加到指针类型中。

如果NULL被定义为nullptr,那么即使你的实验也变得无效了。没有从std::nullptr_t到整数的转换。这就是为什么它被认为是更安全的空指针常量。


为了完整性,0L也是一个空指针常量,并且可以在两种语言中用作“NULL”。 - eerorika
1
@jamesqf 标准规定,值为0的整数常量是空指针常量。因此,0L是一个空指针常量。 - eerorika
1
@eerorika:这正是世界所需要的,忽略现实的标准 :-) 因为如果我还记得我的80286汇编语言正确的话,你甚至不能将一个far指针作为单个操作进行赋值,所以编译器编写者必须特殊处理它。 - jamesqf
3
根据C语言常见问题解答中的说法,将0作为空指针常量的原因是“显然是为了安抚所有存在的写得不好的C代码,这些代码做出了错误的假设”。 - Andrew Henle
3
请注意,C语言中值为0的任何整型常量都被视为空指针常量,并不涉及硬件指针实现。标准C语言也不区分近指针和远指针,但支持指向指针的赋值。此外,它还支持(某些)指针比较,对于如286的分段寻址格式会提出有趣的问题。 - John Bollinger
显示剩余5条评论

22

Am I allowed to use the NULL pointer as a replacement for the value of 0?

int i = NULL;
规则因语言及其版本而异。在某些情况下,你可以使用null指针常量,而在其他情况下则不行。但无论如何,你都不应该这样做。如果你很幸运,编译器会在尝试时发出警告,甚至更好的是,无法编译。
在C++11之前(摘自C++03):
[lib.support.types] NULL在此国际标准中是一个实现定义的C++空指针常量。
将空指针常量用作整数没有多少意义。然而...
[conv.ptr] 一个空指针常量是一个整数类型的整数常量表达式(5.19),其值为零。
因此,即使它是不合理的,从技术上讲,它也可以工作。由于这个技术细节,你可能会遇到滥用NULL的糟糕程序。
自C++11以来(摘自最新草案):
[conv.ptr] 一个空指针常量是一个具有值为零的整数文字([lex.icon])或类型为std::nullptr_t的prvalue。
std::nullptr_t不能转换为整数,因此只有在语言实现选择的情况下,使用NULL作为整数才能工作。
附:nullptr是std::nullptr_t的prvalue。除非你需要在C++11之前编译你的程序,否则应始终使用nullptr而不是NULL。
C有点不同(摘自C11草案N1548):
6.3.2.3语言/转换/其他操作数/指针 3 值为0的整数常量表达式,或者将这样的表达式强制转换为void *类型,被称为null指针常量。...
因此,情况与C++11之后类似,即滥用NULL取决于语言实现所做的选择。

10

是的,不过根据实现方式,你可能需要一个强制类型转换。但它是100%合法的,否则就不会存在。

虽然这样做的风格确实非常糟糕(无需多言?)。

NULL事实上并不是C++,而是C的遗留物。然而,标准中有两个条款([diff.null]和[support.types.nullptr])专门解释了许多C的传统问题,这些条款使NULL在技术上成为C++的一部分。它是一个实现定义的空指针常量。因此,尽管这种做法风格很差,但从技术上讲,它是最符合C++标准的方法。
正如在脚注中指出的那样,可能的实现方式可以是00L,但不能是(void*)0

NULL当然可以使用nullptr代替(虽然标准没有明确说明,但在00L之后几乎没有更好的选择)。这几乎从不发生,但是法律上是可以这样做的。

编译器给您显示的警告表明编译器实际上不是兼容的(除非您在C模式下编译)。因为根据警告,它确实转换了一个空指针(而不是nullptr,后者将是nullptr_t类型,这是不同的),因此很明显NULL的定义确实是(void*)0,尽管它可能并不是。无论哪种情况,您都有两种可能的合法情况(即编译器没有问题)。要么(现实情况)NULL类似于0或0L,然后您有“零或一”次整数转换,您可以继续进行。要么NULL确实是nullptr。在这种情况下,您有一个明确的值,保证比较以及从整数明确定义的转换,但不幸的是不能转换为整数。但是,它确实有一个明确定义的转换为bool(导致false),而bool具有明确定义的转换为整数(导致0)。不幸的是,这是两个转换,因此它不在“零或一”之内,如[conv]所指出的那样。因此,如果您的实现将NULL定义为nullptr,则必须为您的代码添加显式转换才能正确执行。

6

来自 C 语言常见问题解答:

问:如果 NULL 和 0 在空指针常量中等效,我应该使用哪一个?

答:只有在指针上下文中,NULL0 才是等效的。即使可以工作,也不应该在需要其他类型的 0 的情况下使用 NULL,因为这样会发送错误的风格信息。(此外,ANSI 允许将 NULL 定义为 ((void *)0),这在非指针上下文中根本无法工作。)特别地,在需要 ASCII 空字符(NUL)时不要使用 NULL,请提供您自己的定义。

http://c-faq.com/null/nullor0.html


5

免责声明:我不懂C ++。我的答案并非用于C ++环境中。

'\0' 是一个值为零的int,完全等同于0

for (int k = 10; k > '\0'; k--) /* void */;
for (int k = 10; k > 0; k--) /* void */;

在指针的上下文中,0NULL是完全等价的。
if (ptr) /* ... */;
if (ptr != NULL) /* ... */;
if (ptr != '\0') /* ... */;
if (ptr != 0) /* ... */;

所有的内容都是100%等效的。


关于 ptr + NULL 的注意事项

ptr + NULL 的语境不是指针。在 C 语言中没有定义指针的加法操作;指针和整数可以进行加(减)运算。在 ptr + NULL 中,如果 ptr 或者 NULL 其中之一是指针,则另一个必须为整数,因此 ptr + NULL 实际上是 (int)ptr + NULL 或者 ptr + (int)NULL,并且根据 ptrNULL 的定义,可能会出现多种行为:全部正常工作、警告指针和整数之间的转换、无法编译等等。


我之前见过 #define NULL (void *)0。你确定 NULL 和纯 0 是完全等价的吗? - machine_1
2
在指针的上下文中,是的...强调了我的回答中的条件,谢谢。 - pmg
@phuclv:我对C++一窍不通。我的回答(括号中的部分除外)是关于C的。 - pmg
@phuclv ptr + NULL 在指针的上下文中并没有使用 NULL - pmg
"0和NULL是100%等价的" - 不,它们不是。NULL通常(但并非总是)定义为((void*)0),这与0不同。而且绝对不同于nullptr - Jesper Juhl
3
在指针的语境下,它们是完全等效的。我不知道nullptr是什么,但是((void*)0)0(或者是'\0')在指针的语境下是等效的......如果 ptr == '\0' /* 或等价于 0, NULL */ - pmg

5
不再推荐使用NULL(旧的指针初始化方式)。
自C++11起:
关键字nullptr表示指针文字。它是类型为std::nullptr_t的prvalue。存在从nullptr到任何指针类型和任何成员指针类型的空指针值的隐式转换。对于任何空指针常量,包括类型为std::nullptr_t的值以及宏NULL,也存在类似的转换。 https://en.cppreference.com/w/cpp/language/nullptr 实际上,std:: nullptr_t 是空指针文字nullptr的类型。它是不同于指针类型或成员指针类型的类型。
#include <cstddef>
#include <iostream>

void f(int* pi)
{
   std::cout << "Pointer to integer overload\n";
}

void f(double* pd)
{
   std::cout << "Pointer to double overload\n";
}

void f(std::nullptr_t nullp)
{
   std::cout << "null pointer overload\n";
}

int main()
{
    int* pi; double* pd;

    f(pi);
    f(pd);
    f(nullptr);  // would be ambiguous without void f(nullptr_t)
    // f(0);  // ambiguous call: all three functions are candidates
    // f(NULL); // ambiguous if NULL is an integral null pointer constant 
                // (as is the case in most implementations)
}

输出:

Pointer to integer overload
Pointer to double overload
null pointer overload

这个问题是关于将NULL赋值为0的整数。在这种意义上,使用nullptr而不是NULL没有任何变化。 - ivan.ukr
顺便提一下,C++11之后不再有NULL的概念。作者可能对使用constexpr或define旧方式初始化感到困惑。https://en.cppreference.com/w/cpp/language/default_initialization - Build Succeeded

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